@sip-protocol/sdk 0.7.3 → 0.7.4

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 (264) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +267 -0
  3. package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
  4. package/dist/browser.d.mts +10 -4
  5. package/dist/browser.d.ts +10 -4
  6. package/dist/browser.js +47556 -19603
  7. package/dist/browser.mjs +628 -48
  8. package/dist/chunk-4GRJ5MAW.mjs +152 -0
  9. package/dist/chunk-5D7A3L3W.mjs +717 -0
  10. package/dist/chunk-64AYA5F5.mjs +7834 -0
  11. package/dist/chunk-GMDGB22A.mjs +379 -0
  12. package/dist/chunk-I534WKN7.mjs +328 -0
  13. package/dist/chunk-IBZVA5Y7.mjs +1003 -0
  14. package/dist/chunk-PRRZAWJE.mjs +223 -0
  15. package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
  16. package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
  17. package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
  18. package/dist/constants-LHAAUC2T.mjs +51 -0
  19. package/dist/dist-2OGQ7FED.mjs +3957 -0
  20. package/dist/dist-IFHPYLDX.mjs +254 -0
  21. package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
  22. package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
  23. package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
  24. package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
  25. package/dist/index.d.mts +9 -3
  26. package/dist/index.d.ts +9 -3
  27. package/dist/index.js +48396 -19623
  28. package/dist/index.mjs +537 -19
  29. package/dist/interface-Bf7w1PLW.d.mts +679 -0
  30. package/dist/interface-Bf7w1PLW.d.ts +679 -0
  31. package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
  32. package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
  33. package/dist/proofs/halo2.d.mts +151 -0
  34. package/dist/proofs/halo2.d.ts +151 -0
  35. package/dist/proofs/halo2.js +350 -0
  36. package/dist/proofs/halo2.mjs +11 -0
  37. package/dist/proofs/kimchi.d.mts +160 -0
  38. package/dist/proofs/kimchi.d.ts +160 -0
  39. package/dist/proofs/kimchi.js +431 -0
  40. package/dist/proofs/kimchi.mjs +13 -0
  41. package/dist/proofs/noir.d.mts +1 -1
  42. package/dist/proofs/noir.d.ts +1 -1
  43. package/dist/proofs/noir.js +74 -18
  44. package/dist/proofs/noir.mjs +84 -24
  45. package/dist/solana-U3MEGU7W.mjs +280 -0
  46. package/dist/validity_proof-3POXLPNY.mjs +21 -0
  47. package/package.json +54 -21
  48. package/src/adapters/index.ts +41 -0
  49. package/src/adapters/jupiter.ts +571 -0
  50. package/src/adapters/near-intents.ts +135 -0
  51. package/src/advisor/advisor.ts +653 -0
  52. package/src/advisor/index.ts +54 -0
  53. package/src/advisor/tools.ts +303 -0
  54. package/src/advisor/types.ts +164 -0
  55. package/src/chains/ethereum/announcement.ts +536 -0
  56. package/src/chains/ethereum/bnb-optimizations.ts +474 -0
  57. package/src/chains/ethereum/commitment.ts +522 -0
  58. package/src/chains/ethereum/constants.ts +462 -0
  59. package/src/chains/ethereum/deployment.ts +596 -0
  60. package/src/chains/ethereum/gas-estimation.ts +538 -0
  61. package/src/chains/ethereum/index.ts +268 -0
  62. package/src/chains/ethereum/optimizations.ts +614 -0
  63. package/src/chains/ethereum/privacy-adapter.ts +855 -0
  64. package/src/chains/ethereum/registry.ts +584 -0
  65. package/src/chains/ethereum/rpc.ts +905 -0
  66. package/src/chains/ethereum/stealth.ts +491 -0
  67. package/src/chains/ethereum/token.ts +790 -0
  68. package/src/chains/ethereum/transfer.ts +637 -0
  69. package/src/chains/ethereum/types.ts +456 -0
  70. package/src/chains/ethereum/viewing-key.ts +455 -0
  71. package/src/chains/near/commitment.ts +608 -0
  72. package/src/chains/near/constants.ts +284 -0
  73. package/src/chains/near/function-call.ts +871 -0
  74. package/src/chains/near/history.ts +654 -0
  75. package/src/chains/near/implicit-account.ts +840 -0
  76. package/src/chains/near/index.ts +393 -0
  77. package/src/chains/near/native-transfer.ts +658 -0
  78. package/src/chains/near/nep141.ts +775 -0
  79. package/src/chains/near/privacy-adapter.ts +889 -0
  80. package/src/chains/near/resolver.ts +971 -0
  81. package/src/chains/near/rpc.ts +1016 -0
  82. package/src/chains/near/stealth.ts +419 -0
  83. package/src/chains/near/types.ts +317 -0
  84. package/src/chains/near/viewing-key.ts +876 -0
  85. package/src/chains/solana/anchor-transfer.ts +386 -0
  86. package/src/chains/solana/commitment.ts +577 -0
  87. package/src/chains/solana/constants.ts +126 -12
  88. package/src/chains/solana/ephemeral-keys.ts +543 -0
  89. package/src/chains/solana/index.ts +252 -1
  90. package/src/chains/solana/key-derivation.ts +418 -0
  91. package/src/chains/solana/kit-compat.ts +334 -0
  92. package/src/chains/solana/optimizations.ts +560 -0
  93. package/src/chains/solana/privacy-adapter.ts +605 -0
  94. package/src/chains/solana/providers/generic.ts +47 -6
  95. package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
  96. package/src/chains/solana/providers/helius-enhanced.ts +623 -0
  97. package/src/chains/solana/providers/helius.ts +186 -33
  98. package/src/chains/solana/providers/index.ts +31 -0
  99. package/src/chains/solana/providers/interface.ts +61 -18
  100. package/src/chains/solana/providers/quicknode.ts +409 -0
  101. package/src/chains/solana/providers/triton.ts +426 -0
  102. package/src/chains/solana/providers/webhook.ts +338 -67
  103. package/src/chains/solana/rpc-client.ts +1150 -0
  104. package/src/chains/solana/scan.ts +83 -66
  105. package/src/chains/solana/sol-transfer.ts +732 -0
  106. package/src/chains/solana/spl-transfer.ts +886 -0
  107. package/src/chains/solana/stealth-scanner.ts +703 -0
  108. package/src/chains/solana/sunspot-verifier.ts +453 -0
  109. package/src/chains/solana/transaction-builder.ts +755 -0
  110. package/src/chains/solana/transfer.ts +74 -5
  111. package/src/chains/solana/types.ts +57 -6
  112. package/src/chains/solana/utils.ts +110 -0
  113. package/src/chains/solana/viewing-key.ts +807 -0
  114. package/src/compliance/fireblocks.ts +921 -0
  115. package/src/compliance/index.ts +23 -0
  116. package/src/compliance/range-sas.ts +398 -33
  117. package/src/config/endpoints.ts +100 -0
  118. package/src/crypto.ts +11 -8
  119. package/src/errors.ts +82 -0
  120. package/src/evm/erc4337-relayer.ts +830 -0
  121. package/src/evm/index.ts +47 -0
  122. package/src/fees/calculator.ts +396 -0
  123. package/src/fees/index.ts +87 -0
  124. package/src/fees/near-contract.ts +429 -0
  125. package/src/fees/types.ts +268 -0
  126. package/src/index.ts +686 -1
  127. package/src/intent.ts +6 -3
  128. package/src/logger.ts +324 -0
  129. package/src/network/index.ts +80 -0
  130. package/src/network/proxy.ts +691 -0
  131. package/src/optimizations/index.ts +541 -0
  132. package/src/oracle/types.ts +1 -0
  133. package/src/privacy-backends/arcium-types.ts +727 -0
  134. package/src/privacy-backends/arcium.ts +719 -0
  135. package/src/privacy-backends/combined-privacy.ts +866 -0
  136. package/src/privacy-backends/cspl-token.ts +595 -0
  137. package/src/privacy-backends/cspl-types.ts +512 -0
  138. package/src/privacy-backends/cspl.ts +907 -0
  139. package/src/privacy-backends/health.ts +488 -0
  140. package/src/privacy-backends/inco-types.ts +323 -0
  141. package/src/privacy-backends/inco.ts +616 -0
  142. package/src/privacy-backends/index.ts +254 -4
  143. package/src/privacy-backends/interface.ts +649 -6
  144. package/src/privacy-backends/lru-cache.ts +343 -0
  145. package/src/privacy-backends/magicblock.ts +458 -0
  146. package/src/privacy-backends/mock.ts +258 -0
  147. package/src/privacy-backends/privacycash.ts +13 -17
  148. package/src/privacy-backends/private-swap.ts +570 -0
  149. package/src/privacy-backends/rate-limiter.ts +683 -0
  150. package/src/privacy-backends/registry.ts +414 -2
  151. package/src/privacy-backends/router.ts +283 -3
  152. package/src/privacy-backends/shadowwire.ts +449 -0
  153. package/src/privacy-backends/sip-native.ts +3 -0
  154. package/src/privacy-logger.ts +191 -0
  155. package/src/production-safety.ts +373 -0
  156. package/src/proofs/aggregator.ts +1029 -0
  157. package/src/proofs/browser-composer.ts +1150 -0
  158. package/src/proofs/browser.ts +113 -25
  159. package/src/proofs/cache/index.ts +127 -0
  160. package/src/proofs/cache/interface.ts +545 -0
  161. package/src/proofs/cache/key-generator.ts +188 -0
  162. package/src/proofs/cache/lru-cache.ts +481 -0
  163. package/src/proofs/cache/multi-tier-cache.ts +575 -0
  164. package/src/proofs/cache/persistent-cache.ts +788 -0
  165. package/src/proofs/compliance-proof.ts +872 -0
  166. package/src/proofs/composer/base.ts +923 -0
  167. package/src/proofs/composer/index.ts +25 -0
  168. package/src/proofs/composer/interface.ts +518 -0
  169. package/src/proofs/composer/types.ts +383 -0
  170. package/src/proofs/converters/halo2.ts +452 -0
  171. package/src/proofs/converters/index.ts +208 -0
  172. package/src/proofs/converters/interface.ts +363 -0
  173. package/src/proofs/converters/kimchi.ts +462 -0
  174. package/src/proofs/converters/noir.ts +451 -0
  175. package/src/proofs/fallback.ts +888 -0
  176. package/src/proofs/halo2.ts +42 -0
  177. package/src/proofs/index.ts +471 -0
  178. package/src/proofs/interface.ts +13 -0
  179. package/src/proofs/kimchi.ts +42 -0
  180. package/src/proofs/lazy.ts +1004 -0
  181. package/src/proofs/mock.ts +25 -1
  182. package/src/proofs/noir.ts +110 -29
  183. package/src/proofs/orchestrator.ts +960 -0
  184. package/src/proofs/parallel/concurrency.ts +297 -0
  185. package/src/proofs/parallel/dependency-graph.ts +602 -0
  186. package/src/proofs/parallel/executor.ts +420 -0
  187. package/src/proofs/parallel/index.ts +131 -0
  188. package/src/proofs/parallel/interface.ts +685 -0
  189. package/src/proofs/parallel/worker-pool.ts +644 -0
  190. package/src/proofs/providers/halo2.ts +560 -0
  191. package/src/proofs/providers/index.ts +34 -0
  192. package/src/proofs/providers/kimchi.ts +641 -0
  193. package/src/proofs/validator.ts +881 -0
  194. package/src/proofs/verifier.ts +867 -0
  195. package/src/quantum/index.ts +112 -0
  196. package/src/quantum/winternitz-vault.ts +639 -0
  197. package/src/quantum/wots.ts +611 -0
  198. package/src/settlement/backends/direct-chain.ts +1 -0
  199. package/src/settlement/index.ts +9 -0
  200. package/src/settlement/router.ts +732 -46
  201. package/src/solana/index.ts +72 -0
  202. package/src/solana/jito-relayer.ts +687 -0
  203. package/src/solana/noir-verifier-types.ts +430 -0
  204. package/src/solana/noir-verifier.ts +816 -0
  205. package/src/stealth/address-derivation.ts +193 -0
  206. package/src/stealth/ed25519.ts +431 -0
  207. package/src/stealth/index.ts +233 -0
  208. package/src/stealth/meta-address.ts +221 -0
  209. package/src/stealth/secp256k1.ts +368 -0
  210. package/src/stealth/utils.ts +194 -0
  211. package/src/stealth.ts +50 -1504
  212. package/src/sync/index.ts +106 -0
  213. package/src/sync/manager.ts +504 -0
  214. package/src/sync/mock-provider.ts +318 -0
  215. package/src/sync/oblivious.ts +625 -0
  216. package/src/tokens/index.ts +15 -0
  217. package/src/tokens/registry.ts +301 -0
  218. package/src/utils/deprecation.ts +94 -0
  219. package/src/utils/index.ts +9 -0
  220. package/src/wallet/ethereum/index.ts +68 -0
  221. package/src/wallet/ethereum/metamask-privacy.ts +420 -0
  222. package/src/wallet/ethereum/multi-wallet.ts +646 -0
  223. package/src/wallet/ethereum/privacy-adapter.ts +700 -0
  224. package/src/wallet/ethereum/types.ts +3 -1
  225. package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
  226. package/src/wallet/hardware/index.ts +10 -0
  227. package/src/wallet/hardware/ledger-privacy.ts +414 -0
  228. package/src/wallet/index.ts +71 -0
  229. package/src/wallet/near/adapter.ts +626 -0
  230. package/src/wallet/near/index.ts +86 -0
  231. package/src/wallet/near/meteor-wallet.ts +1153 -0
  232. package/src/wallet/near/my-near-wallet.ts +790 -0
  233. package/src/wallet/near/wallet-selector.ts +702 -0
  234. package/src/wallet/solana/adapter.ts +6 -4
  235. package/src/wallet/solana/index.ts +13 -0
  236. package/src/wallet/solana/privacy-adapter.ts +567 -0
  237. package/src/wallet/sui/types.ts +6 -4
  238. package/src/zcash/rpc-client.ts +13 -6
  239. package/dist/chunk-2XIVXWHA.mjs +0 -1930
  240. package/dist/chunk-3INS3PR5.mjs +0 -884
  241. package/dist/chunk-3OVABDRH.mjs +0 -17096
  242. package/dist/chunk-7RFRWDCW.mjs +0 -1504
  243. package/dist/chunk-DLDWZFYC.mjs +0 -1495
  244. package/dist/chunk-E6SZWREQ.mjs +0 -57
  245. package/dist/chunk-F6F73W35.mjs +0 -16166
  246. package/dist/chunk-G33LB27A.mjs +0 -16166
  247. package/dist/chunk-HGU6HZRC.mjs +0 -231
  248. package/dist/chunk-L2K34JCU.mjs +0 -1496
  249. package/dist/chunk-OFDBEIEK.mjs +0 -16166
  250. package/dist/chunk-SF7YSLF5.mjs +0 -1515
  251. package/dist/chunk-SN4ZDTVW.mjs +0 -16166
  252. package/dist/chunk-WWUSGOXE.mjs +0 -17129
  253. package/dist/constants-VOI7BSLK.mjs +0 -27
  254. package/dist/index-B71aXVzk.d.ts +0 -13264
  255. package/dist/index-BYZbDjal.d.ts +0 -11390
  256. package/dist/index-CHB3KuOB.d.mts +0 -11859
  257. package/dist/index-CzWPI6Le.d.ts +0 -11859
  258. package/dist/index-pOIIuwfV.d.mts +0 -13264
  259. package/dist/index-xbWjohNq.d.mts +0 -11390
  260. package/dist/solana-4O4K45VU.mjs +0 -46
  261. package/dist/solana-5EMCTPTS.mjs +0 -46
  262. package/dist/solana-NDABAZ6P.mjs +0 -56
  263. package/dist/solana-Q4NAVBTS.mjs +0 -46
  264. package/dist/solana-ZYO63LY5.mjs +0 -46
@@ -46,8 +46,12 @@ import {
46
46
  import type { StealthAddress } from '@sip-protocol/types'
47
47
  import { parseAnnouncement } from '../types'
48
48
  import type { SolanaScanResult } from '../types'
49
- import { SIP_MEMO_PREFIX, SOLANA_TOKEN_MINTS } from '../constants'
50
- import { ValidationError } from '../../../errors'
49
+ import { SIP_MEMO_PREFIX } from '../constants'
50
+ import { getTokenSymbol, parseTokenTransferFromBalances } from '../utils'
51
+ import { ValidationError, SecurityError } from '../../../errors'
52
+ import { hmac } from '@noble/hashes/hmac'
53
+ import { sha256 } from '@noble/hashes/sha256'
54
+ import { bytesToHex } from '@noble/hashes/utils'
51
55
 
52
56
  /**
53
57
  * Helius raw webhook payload for a transaction
@@ -202,10 +206,19 @@ export type HeliusWebhookPayload =
202
206
  | HeliusEnhancedTransaction[]
203
207
 
204
208
  /**
205
- * Configuration for webhook handler
209
+ * Configuration for the Helius webhook handler
210
+ *
211
+ * @security This config contains sensitive cryptographic keys.
212
+ * Never log, store in plain text, or transmit insecurely.
206
213
  */
207
214
  export interface WebhookHandlerConfig {
208
- /** Recipient's viewing private key (hex) */
215
+ /**
216
+ * Recipient's viewing private key (hex)
217
+ *
218
+ * @security SENSITIVE - This key enables detection of incoming payments.
219
+ * Store securely (encrypted). Never log or expose in error messages.
220
+ * Use environment variables or secure vault in production.
221
+ */
209
222
  viewingPrivateKey: HexString
210
223
  /** Recipient's spending public key (hex) */
211
224
  spendingPublicKey: HexString
@@ -222,6 +235,40 @@ export interface WebhookHandlerConfig {
222
235
  * @param transaction - The transaction that caused the error (if available)
223
236
  */
224
237
  onError?: (error: Error, transaction?: HeliusWebhookTransaction) => void
238
+ /**
239
+ * Webhook authentication secret (recommended for production)
240
+ *
241
+ * When set, the handler will verify the X-Helius-Signature header
242
+ * using HMAC-SHA256 to ensure webhook payloads are authentic.
243
+ *
244
+ * Get your webhook secret from Helius Dashboard:
245
+ * https://dev.helius.xyz/webhooks
246
+ */
247
+ webhookSecret?: string
248
+ /**
249
+ * Authorization token for additional security
250
+ *
251
+ * When set, the handler will verify the Authorization header
252
+ * matches this token. Use for simple auth in trusted environments.
253
+ */
254
+ authToken?: string
255
+ }
256
+
257
+ /**
258
+ * Webhook request with headers for signature verification
259
+ */
260
+ export interface WebhookRequest {
261
+ /** Raw request body as string (for signature verification) */
262
+ rawBody: string
263
+ /** Parsed payload */
264
+ payload: HeliusWebhookPayload
265
+ /** Request headers */
266
+ headers: {
267
+ /** Helius webhook signature (X-Helius-Signature header) */
268
+ signature?: string
269
+ /** Authorization header */
270
+ authorization?: string
271
+ }
225
272
  }
226
273
 
227
274
  /**
@@ -236,34 +283,174 @@ export interface WebhookProcessResult {
236
283
  signature: string
237
284
  }
238
285
 
286
+ /**
287
+ * Verify Helius webhook signature using HMAC-SHA256
288
+ *
289
+ * H-4, H-8 FIX: Implement webhook signature verification
290
+ *
291
+ * @param rawBody - Raw request body as string
292
+ * @param signature - Signature from X-Helius-Signature header
293
+ * @param secret - Webhook secret from Helius dashboard
294
+ * @returns True if signature is valid
295
+ *
296
+ * @example
297
+ * ```typescript
298
+ * const isValid = verifyWebhookSignature(
299
+ * req.rawBody,
300
+ * req.headers['x-helius-signature'],
301
+ * process.env.HELIUS_WEBHOOK_SECRET!
302
+ * )
303
+ * if (!isValid) {
304
+ * res.status(401).send('Invalid signature')
305
+ * return
306
+ * }
307
+ * ```
308
+ */
309
+ export function verifyWebhookSignature(
310
+ rawBody: string,
311
+ signature: string | undefined,
312
+ secret: string
313
+ ): boolean {
314
+ if (!signature || !secret) {
315
+ return false
316
+ }
317
+
318
+ try {
319
+ // Compute expected signature using HMAC-SHA256
320
+ const encoder = new TextEncoder()
321
+ const expectedSignature = bytesToHex(
322
+ hmac(sha256, encoder.encode(secret), encoder.encode(rawBody))
323
+ )
324
+
325
+ // Constant-time comparison to prevent timing attacks
326
+ if (signature.length !== expectedSignature.length) {
327
+ return false
328
+ }
329
+
330
+ let result = 0
331
+ for (let i = 0; i < signature.length; i++) {
332
+ result |= signature.charCodeAt(i) ^ expectedSignature.charCodeAt(i)
333
+ }
334
+ return result === 0
335
+ } catch {
336
+ return false
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Verify authorization token
342
+ *
343
+ * H-7 FIX: Implement authorization verification
344
+ *
345
+ * @param authHeader - Authorization header value
346
+ * @param expectedToken - Expected token
347
+ * @returns True if token matches
348
+ */
349
+ export function verifyAuthToken(
350
+ authHeader: string | undefined,
351
+ expectedToken: string
352
+ ): boolean {
353
+ if (!authHeader || !expectedToken) {
354
+ return false
355
+ }
356
+
357
+ // Support both "Bearer <token>" and raw token formats
358
+ const token = authHeader.startsWith('Bearer ')
359
+ ? authHeader.slice(7)
360
+ : authHeader
361
+
362
+ // Constant-time comparison
363
+ if (token.length !== expectedToken.length) {
364
+ return false
365
+ }
366
+
367
+ let result = 0
368
+ for (let i = 0; i < token.length; i++) {
369
+ result |= token.charCodeAt(i) ^ expectedToken.charCodeAt(i)
370
+ }
371
+ return result === 0
372
+ }
373
+
374
+ /**
375
+ * Handler function type returned by createWebhookHandler
376
+ */
377
+ export interface WebhookHandler {
378
+ /**
379
+ * Process a webhook payload (simple mode - no signature verification)
380
+ */
381
+ (payload: HeliusWebhookPayload): Promise<WebhookProcessResult[]>
382
+
383
+ /**
384
+ * Process a webhook request with full authentication
385
+ *
386
+ * H-4, H-7, H-8 FIX: Supports signature and auth token verification
387
+ */
388
+ processRequest(request: WebhookRequest): Promise<WebhookProcessResult[]>
389
+ }
390
+
239
391
  /**
240
392
  * Create a webhook handler for processing Helius webhook payloads
241
393
  *
242
394
  * @param config - Handler configuration
243
- * @returns Async function to process webhook payloads
395
+ * @returns Handler function to process webhook payloads
244
396
  *
245
- * @example
397
+ * @example Simple usage (not recommended for production)
246
398
  * ```typescript
247
399
  * const handler = createWebhookHandler({
248
400
  * viewingPrivateKey: recipientKeys.viewingPrivateKey,
249
401
  * spendingPublicKey: recipientKeys.spendingPublicKey,
250
402
  * onPaymentFound: async (payment) => {
251
403
  * await db.savePayment(payment)
252
- * await notifyUser(payment)
253
404
  * },
254
405
  * })
255
406
  *
256
- * // In your Express route:
257
407
  * app.post('/webhook', async (req, res) => {
258
408
  * const results = await handler(req.body)
259
409
  * res.json({ processed: results.length })
260
410
  * })
261
411
  * ```
412
+ *
413
+ * @example Production usage with signature verification
414
+ * ```typescript
415
+ * const handler = createWebhookHandler({
416
+ * viewingPrivateKey: recipientKeys.viewingPrivateKey,
417
+ * spendingPublicKey: recipientKeys.spendingPublicKey,
418
+ * webhookSecret: process.env.HELIUS_WEBHOOK_SECRET!,
419
+ * onPaymentFound: async (payment) => {
420
+ * await db.savePayment(payment)
421
+ * },
422
+ * })
423
+ *
424
+ * app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
425
+ * try {
426
+ * const results = await handler.processRequest({
427
+ * rawBody: req.body.toString(),
428
+ * payload: JSON.parse(req.body.toString()),
429
+ * headers: {
430
+ * signature: req.headers['x-helius-signature'] as string,
431
+ * authorization: req.headers['authorization'] as string,
432
+ * },
433
+ * })
434
+ * res.json({ processed: results.length })
435
+ * } catch (error) {
436
+ * if (error instanceof SecurityError) {
437
+ * res.status(401).json({ error: error.message })
438
+ * } else {
439
+ * res.status(500).json({ error: 'Internal error' })
440
+ * }
441
+ * }
442
+ * })
443
+ * ```
262
444
  */
263
- export function createWebhookHandler(
264
- config: WebhookHandlerConfig
265
- ): (payload: HeliusWebhookPayload) => Promise<WebhookProcessResult[]> {
266
- const { viewingPrivateKey, spendingPublicKey, onPaymentFound, onError } = config
445
+ export function createWebhookHandler(config: WebhookHandlerConfig): WebhookHandler {
446
+ const {
447
+ viewingPrivateKey,
448
+ spendingPublicKey,
449
+ onPaymentFound,
450
+ onError,
451
+ webhookSecret,
452
+ authToken,
453
+ } = config
267
454
 
268
455
  // Validate required keys
269
456
  if (!viewingPrivateKey || !viewingPrivateKey.startsWith('0x')) {
@@ -276,9 +463,42 @@ export function createWebhookHandler(
276
463
  throw new ValidationError('onPaymentFound callback is required', 'onPaymentFound')
277
464
  }
278
465
 
279
- return async (payload: HeliusWebhookPayload): Promise<WebhookProcessResult[]> => {
466
+ // Validate key lengths (ed25519 keys are 32 bytes = 64 hex chars + '0x' prefix)
467
+ if (viewingPrivateKey.length !== 66) {
468
+ throw new ValidationError('viewingPrivateKey must be 32 bytes (64 hex characters)', 'viewingPrivateKey')
469
+ }
470
+ if (spendingPublicKey.length !== 66) {
471
+ throw new ValidationError('spendingPublicKey must be 32 bytes (64 hex characters)', 'spendingPublicKey')
472
+ }
473
+
474
+ /**
475
+ * Process transactions after authentication
476
+ */
477
+ async function processTransactions(
478
+ payload: HeliusWebhookPayload
479
+ ): Promise<WebhookProcessResult[]> {
480
+ // H-4 FIX: Validate payload structure before processing
481
+ if (!payload || (typeof payload !== 'object')) {
482
+ throw new ValidationError('Invalid webhook payload', 'payload')
483
+ }
484
+
280
485
  // Normalize to array
281
486
  const transactions = Array.isArray(payload) ? payload : [payload]
487
+
488
+ // Validate we have at least one transaction
489
+ if (transactions.length === 0) {
490
+ return []
491
+ }
492
+
493
+ // Limit batch size to prevent DoS
494
+ const MAX_BATCH_SIZE = 100
495
+ if (transactions.length > MAX_BATCH_SIZE) {
496
+ throw new ValidationError(
497
+ `Batch size exceeds maximum of ${MAX_BATCH_SIZE}`,
498
+ 'payload'
499
+ )
500
+ }
501
+
282
502
  const results: WebhookProcessResult[] = []
283
503
 
284
504
  for (const tx of transactions) {
@@ -312,6 +532,43 @@ export function createWebhookHandler(
312
532
 
313
533
  return results
314
534
  }
535
+
536
+ /**
537
+ * Simple handler (backwards compatible)
538
+ */
539
+ const handler = async (payload: HeliusWebhookPayload): Promise<WebhookProcessResult[]> => {
540
+ return processTransactions(payload)
541
+ }
542
+
543
+ /**
544
+ * Process request with full authentication
545
+ * H-4, H-7, H-8 FIX: Signature and auth verification
546
+ */
547
+ handler.processRequest = async (request: WebhookRequest): Promise<WebhookProcessResult[]> => {
548
+ // Verify webhook signature if configured
549
+ if (webhookSecret) {
550
+ if (!verifyWebhookSignature(request.rawBody, request.headers.signature, webhookSecret)) {
551
+ throw new SecurityError(
552
+ 'Invalid webhook signature - request rejected',
553
+ 'INVALID_SIGNATURE'
554
+ )
555
+ }
556
+ }
557
+
558
+ // Verify auth token if configured
559
+ if (authToken) {
560
+ if (!verifyAuthToken(request.headers.authorization, authToken)) {
561
+ throw new SecurityError(
562
+ 'Invalid authorization token - request rejected',
563
+ 'INVALID_AUTH'
564
+ )
565
+ }
566
+ }
567
+
568
+ return processTransactions(request.payload)
569
+ }
570
+
571
+ return handler as WebhookHandler
315
572
  }
316
573
 
317
574
  /**
@@ -348,13 +605,34 @@ async function processRawTransaction(
348
605
  if (!announcement) continue
349
606
 
350
607
  // Check if this payment is for us
351
- const ephemeralPubKeyHex = solanaAddressToEd25519PublicKey(
352
- announcement.ephemeralPublicKey
353
- )
608
+ // H-5 FIX: Validate ephemeral public key before curve operations
609
+ if (!announcement.ephemeralPublicKey || announcement.ephemeralPublicKey.length < 32) {
610
+ continue // Invalid ephemeral key format, skip
611
+ }
354
612
 
355
- // Parse view tag with bounds checking (view tags are 1 byte: 0-255)
356
- const viewTagNumber = parseInt(announcement.viewTag, 16)
357
- if (!Number.isFinite(viewTagNumber) || viewTagNumber < 0 || viewTagNumber > 255) {
613
+ let ephemeralPubKeyHex: HexString
614
+ try {
615
+ ephemeralPubKeyHex = solanaAddressToEd25519PublicKey(
616
+ announcement.ephemeralPublicKey
617
+ )
618
+ } catch {
619
+ // Invalid base58 encoding or malformed key
620
+ continue
621
+ }
622
+
623
+ // H-3 FIX: Enhanced view tag validation with strict hex parsing
624
+ // View tags are 1 byte (0-255), represented as 2-char hex string
625
+ const viewTagStr = announcement.viewTag
626
+ if (!viewTagStr || typeof viewTagStr !== 'string') {
627
+ continue
628
+ }
629
+ // Validate hex format (only 0-9, a-f, A-F)
630
+ if (!/^[0-9a-fA-F]{1,2}$/.test(viewTagStr)) {
631
+ continue // Invalid hex format
632
+ }
633
+ const viewTagNumber = parseInt(viewTagStr, 16)
634
+ // Double-check bounds after parse (defense in depth)
635
+ if (!Number.isInteger(viewTagNumber) || viewTagNumber < 0 || viewTagNumber > 255) {
358
636
  continue // Invalid view tag, skip this announcement
359
637
  }
360
638
  const stealthAddressToCheck: StealthAddress = {
@@ -379,8 +657,11 @@ async function processRawTransaction(
379
657
  }
380
658
 
381
659
  if (isOurs) {
382
- // Parse token transfer info
383
- const transferInfo = parseTokenTransferFromWebhook(tx)
660
+ // Parse token transfer info using shared utility
661
+ const transferInfo = parseTokenTransferFromBalances(
662
+ tx.meta.preTokenBalances,
663
+ tx.meta.postTokenBalances
664
+ )
384
665
 
385
666
  const payment: SolanaScanResult = {
386
667
  stealthAddress: announcement.stealthAddress || '',
@@ -407,53 +688,17 @@ async function processRawTransaction(
407
688
  return { found: false, signature }
408
689
  }
409
690
 
410
- /**
411
- * Parse token transfer info from webhook transaction
412
- */
413
- function parseTokenTransferFromWebhook(
414
- tx: HeliusWebhookTransaction
415
- ): { mint: string; amount: bigint } | null {
416
- const { preTokenBalances, postTokenBalances } = tx.meta
417
-
418
- if (!postTokenBalances || !preTokenBalances) {
419
- return null
420
- }
421
-
422
- // Find token balance changes
423
- for (const post of postTokenBalances) {
424
- const pre = preTokenBalances.find(
425
- (p) => p.accountIndex === post.accountIndex
426
- )
427
-
428
- const postAmount = BigInt(post.uiTokenAmount.amount)
429
- const preAmount = pre ? BigInt(pre.uiTokenAmount.amount) : 0n
430
-
431
- if (postAmount > preAmount) {
432
- return {
433
- mint: post.mint,
434
- amount: postAmount - preAmount,
435
- }
436
- }
437
- }
438
-
439
- return null
440
- }
441
-
442
- /**
443
- * Get token symbol from mint address
444
- */
445
- function getTokenSymbol(mint: string): string | undefined {
446
- for (const [symbol, address] of Object.entries(SOLANA_TOKEN_MINTS)) {
447
- if (address === mint) {
448
- return symbol
449
- }
450
- }
451
- return undefined
452
- }
691
+ // Token transfer parsing and symbol lookup moved to ../utils.ts (L3 fix)
453
692
 
454
693
  /**
455
- * Type guard for raw transaction
456
- * Raw transactions have transaction.signatures array, enhanced have signature directly
694
+ * Type guard for raw Helius webhook transaction
695
+ *
696
+ * Distinguishes between raw and enhanced transaction formats.
697
+ * Raw transactions have transaction.signatures array, enhanced have signature directly.
698
+ *
699
+ * @param tx - Unknown transaction payload
700
+ * @returns True if tx is a raw HeliusWebhookTransaction
701
+ * @internal
457
702
  */
458
703
  function isRawTransaction(tx: unknown): tx is HeliusWebhookTransaction {
459
704
  return (
@@ -466,7 +711,33 @@ function isRawTransaction(tx: unknown): tx is HeliusWebhookTransaction {
466
711
  }
467
712
 
468
713
  /**
469
- * Get signature from either transaction type
714
+ * Type guard for enhanced Helius webhook transaction
715
+ *
716
+ * Distinguishes between raw and enhanced transaction formats.
717
+ * Enhanced transactions have signature at top level with type field.
718
+ *
719
+ * @param tx - Unknown transaction payload
720
+ * @returns True if tx is an enhanced HeliusEnhancedTransaction
721
+ * @internal
722
+ */
723
+ function isEnhancedTransaction(tx: unknown): tx is HeliusEnhancedTransaction {
724
+ return (
725
+ typeof tx === 'object' &&
726
+ tx !== null &&
727
+ 'signature' in tx &&
728
+ 'type' in tx &&
729
+ typeof (tx as HeliusEnhancedTransaction).signature === 'string'
730
+ )
731
+ }
732
+
733
+ /**
734
+ * Extract transaction signature from webhook payload
735
+ *
736
+ * Handles both raw and enhanced transaction formats.
737
+ *
738
+ * @param tx - Helius webhook transaction (raw or enhanced)
739
+ * @returns Transaction signature, or 'unknown' if not found
740
+ * @internal
470
741
  */
471
742
  function getSignature(tx: HeliusWebhookTransaction | HeliusEnhancedTransaction): string {
472
743
  // Enhanced transactions have signature at top level