@veilo/sdk-core 0.5.0 → 0.7.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 (240) hide show
  1. package/README.md +282 -18
  2. package/accounts.d.ts +2 -0
  3. package/accounts.js +3 -0
  4. package/cloak.d.ts +2 -0
  5. package/cloak.js +3 -0
  6. package/config.d.ts +2 -0
  7. package/config.js +3 -0
  8. package/dist/cjs/events/index.d.ts +6 -0
  9. package/dist/cjs/events/index.js +22 -0
  10. package/dist/cjs/events/parsing.d.ts +25 -0
  11. package/dist/cjs/events/parsing.js +158 -0
  12. package/dist/cjs/events/position.d.ts +26 -0
  13. package/dist/cjs/events/position.js +53 -0
  14. package/dist/cjs/events/recovery.d.ts +87 -0
  15. package/dist/cjs/events/recovery.js +281 -0
  16. package/dist/cjs/events/scanning.d.ts +35 -0
  17. package/dist/cjs/events/scanning.js +98 -0
  18. package/dist/cjs/events/tree.d.ts +27 -0
  19. package/dist/cjs/events/tree.js +63 -0
  20. package/dist/cjs/events/types.d.ts +39 -0
  21. package/dist/cjs/events/types.js +2 -0
  22. package/dist/cjs/events.d.ts +1 -77
  23. package/dist/cjs/events.js +6 -154
  24. package/dist/cjs/identity/encoding.d.ts +9 -0
  25. package/dist/cjs/identity/encoding.js +85 -0
  26. package/dist/cjs/identity/index.d.ts +4 -0
  27. package/dist/cjs/identity/index.js +20 -0
  28. package/dist/cjs/identity/noteKey.d.ts +33 -0
  29. package/dist/cjs/identity/noteKey.js +97 -0
  30. package/dist/cjs/identity/spending.d.ts +18 -0
  31. package/dist/cjs/identity/spending.js +46 -0
  32. package/dist/cjs/identity/viewKey.d.ts +38 -0
  33. package/dist/cjs/identity/viewKey.js +105 -0
  34. package/dist/cjs/idl/index.d.ts +18 -0
  35. package/dist/cjs/idl/index.js +24 -0
  36. package/dist/cjs/idl/privacy_pool.json +9907 -9907
  37. package/dist/cjs/idl/privacy_pool_legacy.d.ts +5 -0
  38. package/dist/cjs/idl/privacy_pool_legacy.js +3307 -0
  39. package/dist/cjs/idl/privacy_pool_legacy.json +2302 -0
  40. package/dist/cjs/idl/privacy_pool_legacy2.d.ts +5 -0
  41. package/dist/cjs/idl/privacy_pool_legacy2.js +3397 -0
  42. package/dist/cjs/idl/privacy_pool_legacy2.json +2376 -0
  43. package/dist/cjs/index.d.ts +8 -3
  44. package/dist/cjs/index.js +12 -2
  45. package/dist/cjs/notes/amount.d.ts +2 -0
  46. package/dist/cjs/notes/amount.js +21 -0
  47. package/dist/cjs/notes/index.d.ts +4 -0
  48. package/dist/cjs/notes/index.js +4 -0
  49. package/dist/cjs/notes/recovery.d.ts +16 -6
  50. package/dist/cjs/notes/recovery.js +33 -16
  51. package/dist/cjs/notes/scan.d.ts +74 -0
  52. package/dist/cjs/notes/scan.js +110 -0
  53. package/dist/cjs/notes/selection.d.ts +133 -0
  54. package/dist/cjs/notes/selection.js +260 -0
  55. package/dist/cjs/notes/spent.d.ts +60 -0
  56. package/dist/cjs/notes/spent.js +101 -0
  57. package/dist/cjs/perps/constants.d.ts +16 -0
  58. package/dist/cjs/perps/constants.js +52 -0
  59. package/dist/cjs/perps/index.d.ts +5 -0
  60. package/dist/cjs/perps/index.js +21 -0
  61. package/dist/cjs/perps/keys.d.ts +16 -0
  62. package/dist/cjs/perps/keys.js +43 -0
  63. package/dist/cjs/perps/pdas.d.ts +29 -0
  64. package/dist/cjs/perps/pdas.js +55 -0
  65. package/dist/cjs/perps/relayer.d.ts +9 -0
  66. package/dist/cjs/perps/relayer.js +35 -0
  67. package/dist/cjs/perps/types.d.ts +126 -0
  68. package/dist/cjs/perps/types.js +2 -0
  69. package/dist/cjs/positions/index.d.ts +4 -0
  70. package/dist/cjs/positions/index.js +20 -0
  71. package/dist/cjs/positions/keys.d.ts +29 -0
  72. package/dist/cjs/positions/keys.js +71 -0
  73. package/dist/cjs/positions/pdas.d.ts +15 -0
  74. package/dist/cjs/positions/pdas.js +54 -0
  75. package/dist/cjs/positions/relayer.d.ts +4 -0
  76. package/dist/cjs/positions/relayer.js +15 -0
  77. package/dist/cjs/positions/types.d.ts +97 -0
  78. package/dist/cjs/positions/types.js +2 -0
  79. package/dist/cjs/predictions/client.d.ts +41 -0
  80. package/dist/cjs/predictions/client.js +147 -0
  81. package/dist/cjs/predictions/index.d.ts +5 -0
  82. package/dist/cjs/predictions/index.js +21 -0
  83. package/dist/cjs/predictions/keys.d.ts +14 -0
  84. package/dist/cjs/predictions/keys.js +40 -0
  85. package/dist/cjs/predictions/pdas.d.ts +6 -0
  86. package/dist/cjs/predictions/pdas.js +13 -0
  87. package/dist/cjs/predictions/relayer.d.ts +3 -0
  88. package/dist/cjs/predictions/relayer.js +11 -0
  89. package/dist/cjs/predictions/types.d.ts +178 -0
  90. package/dist/cjs/predictions/types.js +2 -0
  91. package/dist/cjs/program.d.ts +2 -0
  92. package/dist/cjs/program.js +8 -7
  93. package/dist/cjs/prover.d.ts +27 -4
  94. package/dist/cjs/prover.js +107 -48
  95. package/dist/cjs/relayer/api.d.ts +41 -0
  96. package/dist/cjs/relayer/api.js +99 -0
  97. package/dist/cjs/relayer/client.d.ts +42 -17
  98. package/dist/cjs/relayer/client.js +94 -17
  99. package/dist/cjs/relayer/errors.d.ts +1 -1
  100. package/dist/cjs/relayer/errors.js +1 -1
  101. package/dist/cjs/relayer/index.d.ts +4 -1
  102. package/dist/cjs/relayer/index.js +1 -1
  103. package/dist/cjs/relayer/internal-config.d.ts +36 -0
  104. package/dist/cjs/relayer/internal-config.js +9 -0
  105. package/dist/cjs/relayer/internal.d.ts +6 -0
  106. package/dist/cjs/relayer/internal.js +14 -0
  107. package/dist/cjs/relayer/transport.d.ts +4 -2
  108. package/dist/cjs/relayer/transport.js +10 -5
  109. package/dist/cjs/relayer/types.d.ts +40 -10
  110. package/dist/cjs/relayer/types.js +1 -1
  111. package/dist/cjs/shield/owner.js +1 -4
  112. package/dist/esm/events/index.d.ts +6 -0
  113. package/dist/esm/events/index.js +6 -0
  114. package/dist/esm/events/parsing.d.ts +25 -0
  115. package/dist/esm/events/parsing.js +120 -0
  116. package/dist/esm/events/position.d.ts +26 -0
  117. package/dist/esm/events/position.js +48 -0
  118. package/dist/esm/events/recovery.d.ts +87 -0
  119. package/dist/esm/events/recovery.js +237 -0
  120. package/dist/esm/events/scanning.d.ts +35 -0
  121. package/dist/esm/events/scanning.js +92 -0
  122. package/dist/esm/events/tree.d.ts +27 -0
  123. package/dist/esm/events/tree.js +58 -0
  124. package/dist/esm/events/types.d.ts +39 -0
  125. package/dist/esm/events/types.js +1 -0
  126. package/dist/esm/events.d.ts +1 -77
  127. package/dist/esm/events.js +3 -129
  128. package/dist/esm/identity/encoding.d.ts +9 -0
  129. package/dist/esm/identity/encoding.js +75 -0
  130. package/dist/esm/identity/index.d.ts +4 -0
  131. package/dist/esm/identity/index.js +4 -0
  132. package/dist/esm/identity/noteKey.d.ts +33 -0
  133. package/dist/esm/identity/noteKey.js +91 -0
  134. package/dist/esm/identity/spending.d.ts +18 -0
  135. package/dist/esm/identity/spending.js +35 -0
  136. package/dist/esm/identity/viewKey.d.ts +38 -0
  137. package/dist/esm/identity/viewKey.js +96 -0
  138. package/dist/esm/idl/index.d.ts +18 -0
  139. package/dist/esm/idl/index.js +18 -0
  140. package/dist/esm/idl/privacy_pool.json +9907 -9907
  141. package/dist/esm/idl/privacy_pool_legacy.d.ts +5 -0
  142. package/dist/esm/idl/privacy_pool_legacy.js +3305 -0
  143. package/dist/esm/idl/privacy_pool_legacy.json +2302 -0
  144. package/dist/esm/idl/privacy_pool_legacy2.d.ts +5 -0
  145. package/dist/esm/idl/privacy_pool_legacy2.js +3395 -0
  146. package/dist/esm/idl/privacy_pool_legacy2.json +2376 -0
  147. package/dist/esm/index.d.ts +8 -3
  148. package/dist/esm/index.js +8 -3
  149. package/dist/esm/notes/amount.d.ts +2 -0
  150. package/dist/esm/notes/amount.js +18 -0
  151. package/dist/esm/notes/index.d.ts +4 -0
  152. package/dist/esm/notes/index.js +4 -0
  153. package/dist/esm/notes/recovery.d.ts +16 -6
  154. package/dist/esm/notes/recovery.js +33 -16
  155. package/dist/esm/notes/scan.d.ts +74 -0
  156. package/dist/esm/notes/scan.js +105 -0
  157. package/dist/esm/notes/selection.d.ts +133 -0
  158. package/dist/esm/notes/selection.js +254 -0
  159. package/dist/esm/notes/spent.d.ts +60 -0
  160. package/dist/esm/notes/spent.js +96 -0
  161. package/dist/esm/perps/constants.d.ts +16 -0
  162. package/dist/esm/perps/constants.js +48 -0
  163. package/dist/esm/perps/index.d.ts +5 -0
  164. package/dist/esm/perps/index.js +5 -0
  165. package/dist/esm/perps/keys.d.ts +16 -0
  166. package/dist/esm/perps/keys.js +35 -0
  167. package/dist/esm/perps/pdas.d.ts +29 -0
  168. package/dist/esm/perps/pdas.js +47 -0
  169. package/dist/esm/perps/relayer.d.ts +9 -0
  170. package/dist/esm/perps/relayer.js +25 -0
  171. package/dist/esm/perps/types.d.ts +126 -0
  172. package/dist/esm/perps/types.js +1 -0
  173. package/dist/esm/positions/index.d.ts +4 -0
  174. package/dist/esm/positions/index.js +4 -0
  175. package/dist/esm/positions/keys.d.ts +29 -0
  176. package/dist/esm/positions/keys.js +65 -0
  177. package/dist/esm/positions/pdas.d.ts +15 -0
  178. package/dist/esm/positions/pdas.js +44 -0
  179. package/dist/esm/positions/relayer.d.ts +4 -0
  180. package/dist/esm/positions/relayer.js +10 -0
  181. package/dist/esm/positions/types.d.ts +97 -0
  182. package/dist/esm/positions/types.js +1 -0
  183. package/dist/esm/predictions/client.d.ts +41 -0
  184. package/dist/esm/predictions/client.js +141 -0
  185. package/dist/esm/predictions/index.d.ts +5 -0
  186. package/dist/esm/predictions/index.js +5 -0
  187. package/dist/esm/predictions/keys.d.ts +14 -0
  188. package/dist/esm/predictions/keys.js +33 -0
  189. package/dist/esm/predictions/pdas.d.ts +6 -0
  190. package/dist/esm/predictions/pdas.js +10 -0
  191. package/dist/esm/predictions/relayer.d.ts +3 -0
  192. package/dist/esm/predictions/relayer.js +7 -0
  193. package/dist/esm/predictions/types.d.ts +178 -0
  194. package/dist/esm/predictions/types.js +1 -0
  195. package/dist/esm/program.d.ts +2 -0
  196. package/dist/esm/program.js +4 -3
  197. package/dist/esm/prover.d.ts +27 -4
  198. package/dist/esm/prover.js +106 -15
  199. package/dist/esm/relayer/api.d.ts +41 -0
  200. package/dist/esm/relayer/api.js +66 -0
  201. package/dist/esm/relayer/client.d.ts +42 -17
  202. package/dist/esm/relayer/client.js +94 -17
  203. package/dist/esm/relayer/errors.d.ts +1 -1
  204. package/dist/esm/relayer/errors.js +1 -1
  205. package/dist/esm/relayer/index.d.ts +4 -1
  206. package/dist/esm/relayer/index.js +1 -1
  207. package/dist/esm/relayer/internal-config.d.ts +36 -0
  208. package/dist/esm/relayer/internal-config.js +6 -0
  209. package/dist/esm/relayer/internal.d.ts +6 -0
  210. package/dist/esm/relayer/internal.js +10 -0
  211. package/dist/esm/relayer/transport.d.ts +4 -2
  212. package/dist/esm/relayer/transport.js +10 -5
  213. package/dist/esm/relayer/types.d.ts +40 -10
  214. package/dist/esm/relayer/types.js +1 -1
  215. package/dist/esm/shield/owner.js +1 -4
  216. package/identity.d.ts +2 -0
  217. package/identity.js +3 -0
  218. package/idl.d.ts +2 -0
  219. package/idl.js +3 -0
  220. package/notes.d.ts +2 -0
  221. package/notes.js +3 -0
  222. package/package.json +85 -3
  223. package/perps.d.ts +2 -0
  224. package/perps.js +3 -0
  225. package/poseidon.d.ts +2 -0
  226. package/poseidon.js +3 -0
  227. package/positions.d.ts +2 -0
  228. package/positions.js +3 -0
  229. package/predictions.d.ts +2 -0
  230. package/predictions.js +3 -0
  231. package/proof.d.ts +2 -0
  232. package/proof.js +3 -0
  233. package/prover.d.ts +2 -0
  234. package/prover.js +3 -0
  235. package/relayer.d.ts +2 -0
  236. package/relayer.js +3 -0
  237. package/shield.d.ts +2 -0
  238. package/shield.js +3 -0
  239. package/transactions.d.ts +2 -0
  240. package/transactions.js +3 -0
package/README.md CHANGED
@@ -6,6 +6,7 @@ The SDK gives TypeScript applications a safe, typed way to:
6
6
 
7
7
  - shield SOL or SPL tokens into Veilo;
8
8
  - withdraw, privately transfer, or privately swap shielded funds;
9
+ - open and manage private positions, Jupiter perps, and predictions;
9
10
  - work with Veilo's relayer without handling its encryption protocol yourself;
10
11
  - add partner-powered private sends and swaps through the Cloak API.
11
12
 
@@ -31,7 +32,7 @@ You will also need:
31
32
  - a Solana RPC connection;
32
33
  - a connected wallet when a user must sign;
33
34
  - the Veilo circuit `.wasm` and `.zkey` files for local proof generation;
34
- - a Veilo-issued API key for partner or relayer endpoints that require one.
35
+ - an API key only for partner services such as Cloak or Jupiter Predictions.
35
36
 
36
37
  ## Choose an integration
37
38
 
@@ -39,7 +40,7 @@ You will also need:
39
40
  | --- | --- | --- |
40
41
  | Let any wallet shield funds into Veilo | `shield()` | The user's wallet |
41
42
  | Add private transfers or swaps to a partner app | `VeiloCloakClient` | The user's wallet |
42
- | Build a full Veilo wallet experience | `VeiloRelayerClient` | The relayer for private spends |
43
+ | Build a full Veilo wallet experience | Managed relayer helpers | The relayer for private spends |
43
44
  | Manage proofs, notes, trees, and transactions yourself | Transaction helpers | Your application or relayer |
44
45
 
45
46
  For most third-party deposit integrations, `shield()` is the best starting
@@ -231,31 +232,240 @@ that fits your application:
231
232
  | Private transfer | `submitPrivateTransfer()` | `privateTransfer()` |
232
233
  | Private swap | `submitPrivateSwap()` | `buildPrivateSwapInstructions()` or `transactSwap()` |
233
234
 
234
- ### Use the relayer client
235
+ ### Use the managed relayer helpers
235
236
 
236
- `VeiloRelayerClient` encrypts private transaction requests before sending them
237
- to the relayer. The request and response types are exported for TypeScript
238
- autocomplete.
237
+ The SDK owns Veilo's production relayer URL, encryption key, request encryption,
238
+ and safe retry behavior. Applications call typed functions directly; there is
239
+ no relayer client or URL to configure.
239
240
 
240
241
  ```ts
241
242
  import {
242
- VeiloRelayerClient,
243
+ submitWithdraw,
243
244
  type WithdrawRequest,
244
245
  } from "@veilo/sdk-core/relayer";
245
246
 
246
- const relayer = new VeiloRelayerClient({
247
- apiKey: process.env.VEILO_RELAYER_KEY!,
248
- relayerPublicKey: process.env.VEILO_RELAYER_PUBLIC_KEY!,
249
- authToken: process.env.VEILO_AUTH_TOKEN,
247
+ async function submitWithdrawal(request: WithdrawRequest) {
248
+ return submitWithdraw(request);
249
+ }
250
+ ```
251
+
252
+ The same focused entry point provides account authentication, encrypted note
253
+ storage, Merkle tree reads, private transfers, private swaps, and these product
254
+ flows:
255
+
256
+ | Product | Relayer helpers |
257
+ | --- | --- |
258
+ | Private positions | `submitOpenPosition()`, `submitClosePosition()`, `submitMergePositions()` |
259
+ | Jupiter perps | `submitJperpOpen()`, `submitJperpClose()`, TP/SL, cancellation, recovery, and reissue helpers |
260
+ | Jupiter predictions | `submitPredictionOpen()` and `submitPredictionReissue()` |
261
+
262
+ #### Submission always targets Veilo's relayer
263
+
264
+ Every helper above submits to Veilo's relayer. There is no `baseUrl` option, no
265
+ client to construct, and no supported way to redirect submission. That is
266
+ deliberate, for two independent reasons:
267
+
268
+ 1. **The program requires it.** The privacy pool checks the submitting relayer
269
+ against an on-chain allowlist on every spend. Deposits (`public_amount > 0`)
270
+ are permissionless — which is why `shield()` and `deposit()` work with any
271
+ wallet — but withdrawals, transfers, swaps, positions, perps, and predictions
272
+ all fail with `RelayerNotAllowed` unless the transaction is signed by a
273
+ whitelisted relayer. Pointing the SDK at a different host would produce a
274
+ well-formed request that cannot land on chain.
275
+ 2. **These requests carry spending keys.** `TransactNote.privateKey`, and the
276
+ `claimantSecretKey` on position close and merge, *are* the spend authority for
277
+ the funds involved (see [A private note is valuable secret
278
+ data](#a-private-note-is-valuable-secret-data)). A configurable submission
279
+ target would be a configurable destination for user funds.
280
+
281
+ Running your own relayer is a whitelisting conversation rather than a
282
+ configuration flag — see [Support](#support). Reaching past the package's
283
+ `exports` map to import internal modules is unsupported and not covered by
284
+ semver.
285
+
286
+ ### Finding your notes on the public feed
287
+
288
+ Every row in the compact feed is ciphertext plus a one-byte view tag; nothing on
289
+ it says who a note belongs to. You find yours by trial decryption, and the view
290
+ tag makes that cheap — it rejects roughly 255 of every 256 foreign rows with a
291
+ single hash instead of a full decrypt.
292
+
293
+ ```ts
294
+ import { scanCompactNotes, fetchNotesByCommitment } from "@veilo/sdk-core";
295
+
296
+ const { notes, scanned, nextCursor } = await scanCompactNotes(walletSecretKey, {
297
+ onPage: ({ matched }) => console.log(`${matched} found so far`),
250
298
  });
299
+ ```
251
300
 
252
- async function submitWithdrawal(request: WithdrawRequest) {
253
- return relayer.submitWithdraw(request);
301
+ It paginates to exhaustion by default and derives the X25519 key once for the
302
+ whole scan. Stop early with `maxPages` or by returning `false` from `onPage`;
303
+ `nextCursor` is then non-null and can be passed back later to resume. The feed
304
+ carries only unspent, unclaimed notes, and it needs no auth token — scanning
305
+ reveals nothing without your key.
306
+
307
+ `fetchNotesByCommitment(commitments)` looks up known commitments instead,
308
+ chunked at the server's 50-per-request cap and issued concurrently. Commitments
309
+ the relayer does not know are simply absent from the result.
310
+
311
+ ### Consolidating notes before a large spend
312
+
313
+ When no one or two notes cover an amount, `selectNotesForAmount` reports
314
+ `requiresMerge`. `planNoteConsolidation` says what to do about it:
315
+
316
+ ```ts
317
+ const plan = planNoteConsolidation(unspentNotes, amountRaw, { mint });
318
+ if (plan.ok) {
319
+ for (const { inputs, outputAmount } of plan.steps) {
320
+ // one 2-in-1-out privateTransfer to yourself per step
321
+ }
254
322
  }
255
323
  ```
256
324
 
257
- The same client also provides account authentication, encrypted note storage,
258
- Merkle tree reads, private transfers, and private swaps.
325
+ Each step is a self-transfer combining two inputs into one, so k notes need k-2
326
+ steps before a final two-input spend. Steps chain — a later step can consume an
327
+ earlier step's output, and inputs are tagged `{ kind: "note" }` or
328
+ `{ kind: "step", step }` so you always know which. The two smallest are merged
329
+ each round, which retires dust first and leaves large notes intact. The plan is
330
+ pure: it computes, it does not execute.
331
+
332
+ ### Spend status and private balance
333
+
334
+ Nothing in a note says whether it has been spent — a note is yours until its
335
+ nullifier is published. So a balance is always two steps, and the SDK gives you
336
+ both:
337
+
338
+ ```ts
339
+ import { getPrivateBalance, checkNullifiersSpent } from "@veilo/sdk-core";
340
+
341
+ const { total, byMint, unspent, spent } = await getPrivateBalance(myNotes);
342
+ ```
343
+
344
+ Each note needs an `amount`, a 64-character hex `nullifier`, and optionally a
345
+ `mint` to group by. `getPrivateBalance` asks the relayer which nullifiers are
346
+ spent, then sums what is left; `unspent` and `spent` hand back your own note
347
+ objects, so you can render from them directly.
348
+
349
+ `checkNullifiersSpent(nullifiers)` is the same question on its own. It
350
+ deduplicates, splits into the server's 200-per-request batches, issues them
351
+ concurrently, and returns a `Set` for membership testing. It also lower-cases
352
+ input: the endpoint validates case-insensitively but answers in lower case, so
353
+ upper-case hex would otherwise never match and every note would read as unspent.
354
+
355
+ To compute a balance without the relayer — from a chain scan of nullifier
356
+ events, or a local cache — supply the answer yourself:
357
+
358
+ ```ts
359
+ await getPrivateBalance(myNotes, { resolveSpent: async (ns) => mySpentSet(ns) });
360
+ ```
361
+
362
+ Amounts stay `bigint` end to end. A `number` amount above `Number.MAX_SAFE_INTEGER`
363
+ is rejected rather than silently rounded, and a malformed nullifier throws
364
+ instead of counting as unspent — either would overstate a balance.
365
+
366
+ ### Choosing notes to spend
367
+
368
+ Two rules govern this, and both are easy to get wrong:
369
+
370
+ - the transaction circuit is **2-in-2-out**, so one transaction spends at most
371
+ two notes;
372
+ - notes are only co-spendable inside the **same Merkle tree**, and the tree is
373
+ derived from the mint.
374
+
375
+ ```ts
376
+ import { selectNotesForAmount, canonicalTreeId } from "@veilo/sdk-core";
377
+
378
+ const selection = selectNotesForAmount(unspentNotes, amountRaw, { mint });
379
+ if (!selection.ok) throw new Error(selection.message); // NO_NOTES | INSUFFICIENT_FUNDS
380
+ if (selection.requiresMerge) {
381
+ // more than two inputs — combine them before proving
382
+ }
383
+ ```
384
+
385
+ It searches each tree independently and returns the best result: the smallest
386
+ single note that covers the amount, else the pair with the least change (found
387
+ by a two-pointer scan, not an all-pairs search), else the largest notes greedily
388
+ with `requiresMerge` set. A failure reports `available`, the largest total
389
+ reachable within one tree.
390
+
391
+ `canonicalTreeId(mint)` derives a note's tree the way the relayer does. **Derive
392
+ it; never trust a stored `treeId`.** Change notes are written with the on-chain
393
+ shard index while deposits and synced notes carry the mint-derived id — for
394
+ native SOL both are 0 so the difference hides, but for SPL tokens they diverge
395
+ and co-spending across the two fails at proof time as a commitment mismatch.
396
+
397
+ ### Private product keys and recovery
398
+
399
+ Private positions, perps, and predictions use deterministic client-held keys.
400
+ The SDK provides the exact derivations used by Veilo's wallet, extension,
401
+ relayer, and program tests. Keep the spending key and claimant secret on the
402
+ user's device; send claimant secrets only inside the SDK's encrypted relayer
403
+ requests.
404
+
405
+ ```ts
406
+ import {
407
+ derivePositionKeyBundle,
408
+ deriveSpendingKeyWithSigner,
409
+ encodeClaimantSecretKey,
410
+ initPoseidon,
411
+ submitOpenPosition,
412
+ } from "@veilo/sdk-core";
413
+
414
+ await initPoseidon();
415
+ const spendingKey = await deriveSpendingKeyWithSigner(
416
+ (message) => wallet.signMessage(message),
417
+ );
418
+ const positionKeys = derivePositionKeyBundle(spendingKey, nextPositionIndex);
419
+
420
+ await submitOpenPosition({
421
+ // Select private source notes in your wallet before calling the relayer.
422
+ notes,
423
+ sourceMintAddress,
424
+ destMintAddress,
425
+ swapAmountRaw,
426
+ slippageBps: 50,
427
+ userPublicKey: wallet.publicKey.toBase58(),
428
+ veiloPublicKey,
429
+ position: positionKeys.position,
430
+ });
431
+
432
+ // Persist the returned position data and `nextPositionIndex`. Re-derive the
433
+ // same claimant later when closing or merging the position.
434
+ const claimantSecretKey = encodeClaimantSecretKey(positionKeys.claimant);
435
+ ```
436
+
437
+ Use `deriveJperpKeyBundle()` and `getJperpMarketPayload()` for private perps.
438
+ Use `derivePredictionKeyBundle()` for private predictions. Lower-level key
439
+ derivations are also exported when an application needs them individually. The
440
+ PDA helpers in each focused entry point support on-chain recovery scans on a
441
+ fresh device.
442
+
443
+ Predictions have one additional step: Jupiter returns an unsigned order,
444
+ close, or claim transaction. The SDK exposes that API without taking custody
445
+ of signing:
446
+
447
+ ```ts
448
+ import {
449
+ JupiterPredictionClient,
450
+ deserializePredictionTransaction,
451
+ } from "@veilo/sdk-core/predictions";
452
+
453
+ const predictions = new JupiterPredictionClient({ apiKey: jupiterApiKey });
454
+ const order = await predictions.placeOrder({
455
+ ownerPubkey: ephemeral.publicKey.toBase58(),
456
+ marketId,
457
+ isYes: true,
458
+ isBuy: true,
459
+ depositAmount: "5000000",
460
+ depositMint: usdcMint,
461
+ });
462
+ const transaction = deserializePredictionTransaction(order);
463
+ // Ask the deterministic ephemeral wallet to sign, then submit and confirm it.
464
+ ```
465
+
466
+ The SDK deliberately does not own application storage or choose which notes to
467
+ spend. Persist indexes and returned product records only after the corresponding
468
+ transaction is confirmed.
259
469
 
260
470
  ### Use the direct helpers
261
471
 
@@ -347,8 +557,28 @@ for shields and call a deposit's `commit()` callback only after confirmation.
347
557
  ### Proof files are separate
348
558
 
349
559
  Circuit `.wasm` and `.zkey` files are intentionally not bundled with the npm
350
- package because they are large. Provide local paths, byte arrays, or hosted URLs
351
- to `createTransactionProver()` and `createSwapProver()`.
560
+ package because they are large `files` ships `dist/` only. Provide local
561
+ paths, byte arrays, or hosted URLs to `createTransactionProver()` and
562
+ `createSwapProver()`.
563
+
564
+ Both arguments are optional. Omit them and the prover resolves artifacts from
565
+ disk on first use, first hit wins:
566
+
567
+ 1. `$VEILO_CIRCUITS_DIR`
568
+ 2. `<package>/circuits` — populated by `npm run copy:circuits`
569
+ 3. `<cwd>/circuits`
570
+ 4. `<cwd>/node_modules/@veilo/sdk-core/circuits`
571
+
572
+ ```ts
573
+ const prover = createTransactionProver(); // resolved from disk
574
+ const prover = createTransactionProver({ wasmPath, zkeyPath }); // explicit
575
+ ```
576
+
577
+ `resolveCircuitArtifacts("transaction" | "swap")` performs that lookup on its
578
+ own if you want the paths. Resolution is **Node-only and lazy** — it imports
579
+ `node:fs` inside the call so browser bundles can still import this module, and
580
+ there is nothing to resolve in a browser anyway. Browser builds must pass
581
+ `CircuitArtifacts` explicitly. A miss throws with every path it tried.
352
582
 
353
583
  ## Error handling
354
584
 
@@ -388,8 +618,11 @@ import { shield } from "@veilo/sdk-core";
388
618
  import { getPoolPdas } from "@veilo/sdk-core/accounts";
389
619
  import { createUTXO } from "@veilo/sdk-core/notes";
390
620
  import { deposit, withdraw } from "@veilo/sdk-core/transactions";
391
- import { VeiloRelayerClient } from "@veilo/sdk-core/relayer";
621
+ import { submitWithdraw } from "@veilo/sdk-core/relayer";
392
622
  import { VeiloCloakClient } from "@veilo/sdk-core/cloak";
623
+ import { derivePositionKeyBundle } from "@veilo/sdk-core/positions";
624
+ import { getJperpMarketPayload } from "@veilo/sdk-core/perps";
625
+ import { JupiterPredictionClient } from "@veilo/sdk-core/predictions";
393
626
  ```
394
627
 
395
628
  Available focused entry points:
@@ -397,7 +630,12 @@ Available focused entry points:
397
630
  - `@veilo/sdk-core/accounts`
398
631
  - `@veilo/sdk-core/cloak`
399
632
  - `@veilo/sdk-core/config`
633
+ - `@veilo/sdk-core/idl`
634
+ - `@veilo/sdk-core/identity`
400
635
  - `@veilo/sdk-core/notes`
636
+ - `@veilo/sdk-core/perps`
637
+ - `@veilo/sdk-core/positions`
638
+ - `@veilo/sdk-core/predictions`
401
639
  - `@veilo/sdk-core/poseidon`
402
640
  - `@veilo/sdk-core/proof`
403
641
  - `@veilo/sdk-core/prover`
@@ -421,6 +659,32 @@ or relayer:
421
659
  These APIs are fully typed. Use their TypeScript definitions as the source of
422
660
  truth for required inputs.
423
661
 
662
+ ### Historical IDLs and commitment recovery
663
+
664
+ The SDK includes the current, legacy, and legacy-2 privacy-pool IDLs used by the
665
+ relayer. Event scans automatically try the historical layouts, so applications
666
+ can rebuild trees across program upgrades.
667
+
668
+ ```ts
669
+ import {
670
+ PRIVACY_POOL_IDLS,
671
+ buildTreeFromEvents,
672
+ resolveCommitmentEvents,
673
+ } from "@veilo/sdk-core";
674
+ ```
675
+
676
+ `buildTreeFromEvents()` includes Veilo's protected historical repairs by
677
+ default and rejects gaps that would produce an incorrect root. When a confirmed
678
+ instruction appended commitments without emitting `CommitmentEvent`, use
679
+ `resolveCommitmentEvents()` with the expected commitments and current local
680
+ tree. It prefers emitted metadata and computes only the missing entries.
681
+
682
+ For the deployed eventless `open_position` path, use
683
+ `recoverEventlessOpenPositionEvents()`. It decodes both commitments from the
684
+ instruction and reads the authoritative global position-tree leaf index from
685
+ the program-owned PositionPDA. `buildGlobalPositionTreeFromEvents()` mirrors
686
+ the relayer's all-mint position-tree reconstruction.
687
+
424
688
  ## Development
425
689
 
426
690
  ```bash
package/accounts.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ // Generated by scripts/gen-subpath-stubs.mjs — do not edit.
2
+ export * from "./dist/cjs/accounts/index";
package/accounts.js ADDED
@@ -0,0 +1,3 @@
1
+ // Generated by scripts/gen-subpath-stubs.mjs — do not edit.
2
+ // Resolves "@veilo/sdk-core/accounts" for legacy moduleResolution: "node".
3
+ module.exports = require("./dist/cjs/accounts/index.js");
package/cloak.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ // Generated by scripts/gen-subpath-stubs.mjs — do not edit.
2
+ export * from "./dist/cjs/cloak";
package/cloak.js ADDED
@@ -0,0 +1,3 @@
1
+ // Generated by scripts/gen-subpath-stubs.mjs — do not edit.
2
+ // Resolves "@veilo/sdk-core/cloak" for legacy moduleResolution: "node".
3
+ module.exports = require("./dist/cjs/cloak.js");
package/config.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ // Generated by scripts/gen-subpath-stubs.mjs — do not edit.
2
+ export * from "./dist/cjs/config";
package/config.js ADDED
@@ -0,0 +1,3 @@
1
+ // Generated by scripts/gen-subpath-stubs.mjs — do not edit.
2
+ // Resolves "@veilo/sdk-core/config" for legacy moduleResolution: "node".
3
+ module.exports = require("./dist/cjs/config.js");
@@ -0,0 +1,6 @@
1
+ export * from "./types.js";
2
+ export * from "./parsing.js";
3
+ export * from "./position.js";
4
+ export * from "./recovery.js";
5
+ export * from "./scanning.js";
6
+ export * from "./tree.js";
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types.js"), exports);
18
+ __exportStar(require("./parsing.js"), exports);
19
+ __exportStar(require("./position.js"), exports);
20
+ __exportStar(require("./recovery.js"), exports);
21
+ __exportStar(require("./scanning.js"), exports);
22
+ __exportStar(require("./tree.js"), exports);
@@ -0,0 +1,25 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import { type VersionedPrivacyPoolIdl } from "../idl/index.js";
3
+ import type { CommitmentEvent, NullifierSpentEvent, ParsedVeiloEvent } from "./types.js";
4
+ /**
5
+ * Decode logs using the current, legacy, and legacy-2 IDLs.
6
+ *
7
+ * IDLs are evaluated in the relayer's current/legacy/legacy-2 order. Anchor's
8
+ * Borsh decoder accepts trailing bytes, however, so every successful parse is
9
+ * considered and the richest result wins. That preserves the compact fields
10
+ * in legacy-2 events instead of misclassifying them as the shorter legacy form.
11
+ * Ties retain the relayer's order.
12
+ */
13
+ export declare function parseVeiloEventsWithFallback(logMessages: readonly string[] | null | undefined, programId: PublicKey, idls?: readonly VersionedPrivacyPoolIdl[]): ParsedVeiloEvent[];
14
+ export declare function parseCommitmentEventsFromLogs(params: {
15
+ logMessages: readonly string[] | null | undefined;
16
+ programId: PublicKey;
17
+ txSignature: string;
18
+ slot: number;
19
+ }): CommitmentEvent[];
20
+ export declare function parseNullifierEventsFromLogs(params: {
21
+ logMessages: readonly string[] | null | undefined;
22
+ programId: PublicKey;
23
+ txSignature: string;
24
+ slot: number;
25
+ }): NullifierSpentEvent[];
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.parseVeiloEventsWithFallback = parseVeiloEventsWithFallback;
37
+ exports.parseCommitmentEventsFromLogs = parseCommitmentEventsFromLogs;
38
+ exports.parseNullifierEventsFromLogs = parseNullifierEventsFromLogs;
39
+ const anchor = __importStar(require("@coral-xyz/anchor"));
40
+ const web3_js_1 = require("@solana/web3.js");
41
+ const index_js_1 = require("../idl/index.js");
42
+ /**
43
+ * Decode logs using the current, legacy, and legacy-2 IDLs.
44
+ *
45
+ * IDLs are evaluated in the relayer's current/legacy/legacy-2 order. Anchor's
46
+ * Borsh decoder accepts trailing bytes, however, so every successful parse is
47
+ * considered and the richest result wins. That preserves the compact fields
48
+ * in legacy-2 events instead of misclassifying them as the shorter legacy form.
49
+ * Ties retain the relayer's order.
50
+ */
51
+ function parseVeiloEventsWithFallback(logMessages, programId, idls = index_js_1.PRIVACY_POOL_IDLS) {
52
+ if (!logMessages?.length)
53
+ return [];
54
+ let best = [];
55
+ let bestScore = -1;
56
+ for (const candidate of idls) {
57
+ try {
58
+ const parser = new anchor.EventParser(programId, new anchor.BorshCoder(candidate.idl));
59
+ const parsed = Array.from(parser.parseLogs([...logMessages])).map((event) => ({
60
+ name: event.name,
61
+ data: event.data,
62
+ idlVersion: candidate.version,
63
+ }));
64
+ const score = parsed.reduce((total, event) => total + decodedFieldCount(event.data), 0);
65
+ if (score > bestScore) {
66
+ best = parsed;
67
+ bestScore = score;
68
+ }
69
+ }
70
+ catch {
71
+ // The discriminator is shared while historical payload lengths differ.
72
+ }
73
+ }
74
+ return best;
75
+ }
76
+ function parseCommitmentEventsFromLogs(params) {
77
+ const { logMessages, programId, txSignature, slot } = params;
78
+ const out = [];
79
+ for (const event of parseVeiloEventsWithFallback(logMessages, programId)) {
80
+ if (event.name.toLowerCase() !== "commitmentevent")
81
+ continue;
82
+ const data = event.data;
83
+ const mintAddress = toPublicKey(field(data, "mintAddress", "mint_address"));
84
+ const ephemeralPublicKey = optionalBytes(field(data, "ephemeralPublicKey", "ephemeral_public_key"));
85
+ const encryptedBlob = optionalBytes(field(data, "encryptedBlob", "encrypted_blob"));
86
+ const rawViewTag = field(data, "viewTag", "view_tag");
87
+ out.push({
88
+ commitment: toBytes(field(data, "commitment"), 32, "commitment"),
89
+ leafIndex: toSafeNumber(field(data, "leafIndex", "leaf_index"), "leafIndex"),
90
+ newRoot: toBytes(field(data, "newRoot", "new_root"), 32, "newRoot"),
91
+ timestamp: toSafeNumber(field(data, "timestamp"), "timestamp"),
92
+ mintAddress,
93
+ treeId: toSafeNumber(field(data, "treeId", "tree_id"), "treeId"),
94
+ txSignature,
95
+ slot,
96
+ ...(ephemeralPublicKey ? { ephemeralPublicKey } : {}),
97
+ ...(encryptedBlob ? { encryptedBlob } : {}),
98
+ ...(rawViewTag !== undefined
99
+ ? { viewTag: toSafeNumber(rawViewTag, "viewTag") }
100
+ : {}),
101
+ source: "emitted",
102
+ idlVersion: event.idlVersion,
103
+ });
104
+ }
105
+ return out;
106
+ }
107
+ function parseNullifierEventsFromLogs(params) {
108
+ const { logMessages, programId, txSignature, slot } = params;
109
+ const out = [];
110
+ for (const event of parseVeiloEventsWithFallback(logMessages, programId)) {
111
+ if (event.name.toLowerCase() !== "nullifierspent")
112
+ continue;
113
+ const data = event.data;
114
+ out.push({
115
+ nullifier: toBytes(field(data, "nullifier"), 32, "nullifier"),
116
+ timestamp: toSafeNumber(field(data, "timestamp"), "timestamp"),
117
+ mintAddress: toPublicKey(field(data, "mintAddress", "mint_address")),
118
+ treeId: toSafeNumber(field(data, "treeId", "tree_id"), "treeId"),
119
+ txSignature,
120
+ slot,
121
+ idlVersion: event.idlVersion,
122
+ });
123
+ }
124
+ return out;
125
+ }
126
+ function field(data, camel, snake) {
127
+ return data[camel] ?? (snake ? data[snake] : undefined);
128
+ }
129
+ function optionalBytes(value) {
130
+ return value === undefined || value === null
131
+ ? undefined
132
+ : new Uint8Array(value);
133
+ }
134
+ function decodedFieldCount(value) {
135
+ return value && typeof value === "object" ? Object.keys(value).length : 0;
136
+ }
137
+ function toBytes(value, length, label) {
138
+ const bytes = new Uint8Array(value);
139
+ if (bytes.length !== length) {
140
+ throw new Error(`${label} must be ${length} bytes, got ${bytes.length}`);
141
+ }
142
+ return bytes;
143
+ }
144
+ function toPublicKey(value) {
145
+ if (value instanceof web3_js_1.PublicKey)
146
+ return value;
147
+ return new web3_js_1.PublicKey(value);
148
+ }
149
+ function toSafeNumber(value, label) {
150
+ if (value === undefined || value === null) {
151
+ throw new Error(`Missing ${label} in decoded event`);
152
+ }
153
+ const numeric = typeof value === "number" ? value : Number(value);
154
+ if (!Number.isSafeInteger(numeric)) {
155
+ throw new Error(`${label} is outside JavaScript's safe integer range`);
156
+ }
157
+ return numeric;
158
+ }
@@ -0,0 +1,26 @@
1
+ import type { Idl, Program } from "@coral-xyz/anchor";
2
+ import { PublicKey } from "@solana/web3.js";
3
+ import type { CommitmentEvent } from "./types.js";
4
+ /**
5
+ * Privacy-pool mints copied from the relayer's global position-tree classifier.
6
+ * Position commitments use non-pool destination mints at the same tree ID.
7
+ */
8
+ export declare const POSITION_TREE_POOL_MINTS: readonly PublicKey[];
9
+ export declare function isGlobalPositionCommitment(event: CommitmentEvent): boolean;
10
+ /**
11
+ * Rebuild the one global position tree shared across every position mint.
12
+ * This mirrors the relayer: scan all mints for the position tree ID, then drop
13
+ * normal privacy-pool mint events before sorting by the global leaf index.
14
+ */
15
+ export declare function buildGlobalPositionTreeFromEvents<T extends Idl>(params: {
16
+ program: Program<T>;
17
+ positionTreeId?: number;
18
+ depth?: number;
19
+ additionalEvents?: readonly CommitmentEvent[];
20
+ includeProtectedEvents?: boolean;
21
+ strictLeafIndices?: boolean;
22
+ }): Promise<{
23
+ tree: import("../merkle.js").MerkleTree;
24
+ events: CommitmentEvent[];
25
+ latestSignature: string;
26
+ }>;