@sip-protocol/sdk 0.7.3 → 0.8.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 (263) hide show
  1. package/README.md +267 -0
  2. package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
  3. package/dist/browser.d.mts +10 -4
  4. package/dist/browser.d.ts +10 -4
  5. package/dist/browser.js +47556 -19603
  6. package/dist/browser.mjs +628 -48
  7. package/dist/chunk-4GRJ5MAW.mjs +152 -0
  8. package/dist/chunk-5D7A3L3W.mjs +717 -0
  9. package/dist/chunk-64AYA5F5.mjs +7834 -0
  10. package/dist/chunk-GMDGB22A.mjs +379 -0
  11. package/dist/chunk-I534WKN7.mjs +328 -0
  12. package/dist/chunk-IBZVA5Y7.mjs +1003 -0
  13. package/dist/chunk-PRRZAWJE.mjs +223 -0
  14. package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
  15. package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
  16. package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
  17. package/dist/constants-LHAAUC2T.mjs +51 -0
  18. package/dist/dist-2OGQ7FED.mjs +3957 -0
  19. package/dist/dist-IFHPYLDX.mjs +254 -0
  20. package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
  21. package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
  22. package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
  23. package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
  24. package/dist/index.d.mts +9 -3
  25. package/dist/index.d.ts +9 -3
  26. package/dist/index.js +48396 -19623
  27. package/dist/index.mjs +537 -19
  28. package/dist/interface-Bf7w1PLW.d.mts +679 -0
  29. package/dist/interface-Bf7w1PLW.d.ts +679 -0
  30. package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
  31. package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
  32. package/dist/proofs/halo2.d.mts +151 -0
  33. package/dist/proofs/halo2.d.ts +151 -0
  34. package/dist/proofs/halo2.js +350 -0
  35. package/dist/proofs/halo2.mjs +11 -0
  36. package/dist/proofs/kimchi.d.mts +160 -0
  37. package/dist/proofs/kimchi.d.ts +160 -0
  38. package/dist/proofs/kimchi.js +431 -0
  39. package/dist/proofs/kimchi.mjs +13 -0
  40. package/dist/proofs/noir.d.mts +1 -1
  41. package/dist/proofs/noir.d.ts +1 -1
  42. package/dist/proofs/noir.js +74 -18
  43. package/dist/proofs/noir.mjs +84 -24
  44. package/dist/solana-U3MEGU7W.mjs +280 -0
  45. package/dist/validity_proof-3POXLPNY.mjs +21 -0
  46. package/package.json +44 -11
  47. package/src/adapters/index.ts +41 -0
  48. package/src/adapters/jupiter.ts +571 -0
  49. package/src/adapters/near-intents.ts +135 -0
  50. package/src/advisor/advisor.ts +653 -0
  51. package/src/advisor/index.ts +54 -0
  52. package/src/advisor/tools.ts +303 -0
  53. package/src/advisor/types.ts +164 -0
  54. package/src/chains/ethereum/announcement.ts +536 -0
  55. package/src/chains/ethereum/bnb-optimizations.ts +474 -0
  56. package/src/chains/ethereum/commitment.ts +522 -0
  57. package/src/chains/ethereum/constants.ts +462 -0
  58. package/src/chains/ethereum/deployment.ts +596 -0
  59. package/src/chains/ethereum/gas-estimation.ts +538 -0
  60. package/src/chains/ethereum/index.ts +268 -0
  61. package/src/chains/ethereum/optimizations.ts +614 -0
  62. package/src/chains/ethereum/privacy-adapter.ts +855 -0
  63. package/src/chains/ethereum/registry.ts +584 -0
  64. package/src/chains/ethereum/rpc.ts +905 -0
  65. package/src/chains/ethereum/stealth.ts +491 -0
  66. package/src/chains/ethereum/token.ts +790 -0
  67. package/src/chains/ethereum/transfer.ts +637 -0
  68. package/src/chains/ethereum/types.ts +456 -0
  69. package/src/chains/ethereum/viewing-key.ts +455 -0
  70. package/src/chains/near/commitment.ts +608 -0
  71. package/src/chains/near/constants.ts +284 -0
  72. package/src/chains/near/function-call.ts +871 -0
  73. package/src/chains/near/history.ts +654 -0
  74. package/src/chains/near/implicit-account.ts +840 -0
  75. package/src/chains/near/index.ts +393 -0
  76. package/src/chains/near/native-transfer.ts +658 -0
  77. package/src/chains/near/nep141.ts +775 -0
  78. package/src/chains/near/privacy-adapter.ts +889 -0
  79. package/src/chains/near/resolver.ts +971 -0
  80. package/src/chains/near/rpc.ts +1016 -0
  81. package/src/chains/near/stealth.ts +419 -0
  82. package/src/chains/near/types.ts +317 -0
  83. package/src/chains/near/viewing-key.ts +876 -0
  84. package/src/chains/solana/anchor-transfer.ts +386 -0
  85. package/src/chains/solana/commitment.ts +577 -0
  86. package/src/chains/solana/constants.ts +126 -12
  87. package/src/chains/solana/ephemeral-keys.ts +543 -0
  88. package/src/chains/solana/index.ts +252 -1
  89. package/src/chains/solana/key-derivation.ts +418 -0
  90. package/src/chains/solana/kit-compat.ts +334 -0
  91. package/src/chains/solana/optimizations.ts +560 -0
  92. package/src/chains/solana/privacy-adapter.ts +605 -0
  93. package/src/chains/solana/providers/generic.ts +47 -6
  94. package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
  95. package/src/chains/solana/providers/helius-enhanced.ts +623 -0
  96. package/src/chains/solana/providers/helius.ts +186 -33
  97. package/src/chains/solana/providers/index.ts +31 -0
  98. package/src/chains/solana/providers/interface.ts +61 -18
  99. package/src/chains/solana/providers/quicknode.ts +409 -0
  100. package/src/chains/solana/providers/triton.ts +426 -0
  101. package/src/chains/solana/providers/webhook.ts +338 -67
  102. package/src/chains/solana/rpc-client.ts +1150 -0
  103. package/src/chains/solana/scan.ts +83 -66
  104. package/src/chains/solana/sol-transfer.ts +732 -0
  105. package/src/chains/solana/spl-transfer.ts +886 -0
  106. package/src/chains/solana/stealth-scanner.ts +703 -0
  107. package/src/chains/solana/sunspot-verifier.ts +453 -0
  108. package/src/chains/solana/transaction-builder.ts +755 -0
  109. package/src/chains/solana/transfer.ts +74 -5
  110. package/src/chains/solana/types.ts +57 -6
  111. package/src/chains/solana/utils.ts +110 -0
  112. package/src/chains/solana/viewing-key.ts +807 -0
  113. package/src/compliance/fireblocks.ts +921 -0
  114. package/src/compliance/index.ts +23 -0
  115. package/src/compliance/range-sas.ts +398 -33
  116. package/src/config/endpoints.ts +100 -0
  117. package/src/crypto.ts +11 -8
  118. package/src/errors.ts +82 -0
  119. package/src/evm/erc4337-relayer.ts +830 -0
  120. package/src/evm/index.ts +47 -0
  121. package/src/fees/calculator.ts +396 -0
  122. package/src/fees/index.ts +87 -0
  123. package/src/fees/near-contract.ts +429 -0
  124. package/src/fees/types.ts +268 -0
  125. package/src/index.ts +686 -1
  126. package/src/intent.ts +6 -3
  127. package/src/logger.ts +324 -0
  128. package/src/network/index.ts +80 -0
  129. package/src/network/proxy.ts +691 -0
  130. package/src/optimizations/index.ts +541 -0
  131. package/src/oracle/types.ts +1 -0
  132. package/src/privacy-backends/arcium-types.ts +727 -0
  133. package/src/privacy-backends/arcium.ts +719 -0
  134. package/src/privacy-backends/combined-privacy.ts +866 -0
  135. package/src/privacy-backends/cspl-token.ts +595 -0
  136. package/src/privacy-backends/cspl-types.ts +512 -0
  137. package/src/privacy-backends/cspl.ts +907 -0
  138. package/src/privacy-backends/health.ts +488 -0
  139. package/src/privacy-backends/inco-types.ts +323 -0
  140. package/src/privacy-backends/inco.ts +616 -0
  141. package/src/privacy-backends/index.ts +254 -4
  142. package/src/privacy-backends/interface.ts +649 -6
  143. package/src/privacy-backends/lru-cache.ts +343 -0
  144. package/src/privacy-backends/magicblock.ts +458 -0
  145. package/src/privacy-backends/mock.ts +258 -0
  146. package/src/privacy-backends/privacycash.ts +13 -17
  147. package/src/privacy-backends/private-swap.ts +570 -0
  148. package/src/privacy-backends/rate-limiter.ts +683 -0
  149. package/src/privacy-backends/registry.ts +414 -2
  150. package/src/privacy-backends/router.ts +283 -3
  151. package/src/privacy-backends/shadowwire.ts +449 -0
  152. package/src/privacy-backends/sip-native.ts +3 -0
  153. package/src/privacy-logger.ts +191 -0
  154. package/src/production-safety.ts +373 -0
  155. package/src/proofs/aggregator.ts +1029 -0
  156. package/src/proofs/browser-composer.ts +1150 -0
  157. package/src/proofs/browser.ts +113 -25
  158. package/src/proofs/cache/index.ts +127 -0
  159. package/src/proofs/cache/interface.ts +545 -0
  160. package/src/proofs/cache/key-generator.ts +188 -0
  161. package/src/proofs/cache/lru-cache.ts +481 -0
  162. package/src/proofs/cache/multi-tier-cache.ts +575 -0
  163. package/src/proofs/cache/persistent-cache.ts +788 -0
  164. package/src/proofs/compliance-proof.ts +872 -0
  165. package/src/proofs/composer/base.ts +923 -0
  166. package/src/proofs/composer/index.ts +25 -0
  167. package/src/proofs/composer/interface.ts +518 -0
  168. package/src/proofs/composer/types.ts +383 -0
  169. package/src/proofs/converters/halo2.ts +452 -0
  170. package/src/proofs/converters/index.ts +208 -0
  171. package/src/proofs/converters/interface.ts +363 -0
  172. package/src/proofs/converters/kimchi.ts +462 -0
  173. package/src/proofs/converters/noir.ts +451 -0
  174. package/src/proofs/fallback.ts +888 -0
  175. package/src/proofs/halo2.ts +42 -0
  176. package/src/proofs/index.ts +471 -0
  177. package/src/proofs/interface.ts +13 -0
  178. package/src/proofs/kimchi.ts +42 -0
  179. package/src/proofs/lazy.ts +1004 -0
  180. package/src/proofs/mock.ts +25 -1
  181. package/src/proofs/noir.ts +110 -29
  182. package/src/proofs/orchestrator.ts +960 -0
  183. package/src/proofs/parallel/concurrency.ts +297 -0
  184. package/src/proofs/parallel/dependency-graph.ts +602 -0
  185. package/src/proofs/parallel/executor.ts +420 -0
  186. package/src/proofs/parallel/index.ts +131 -0
  187. package/src/proofs/parallel/interface.ts +685 -0
  188. package/src/proofs/parallel/worker-pool.ts +644 -0
  189. package/src/proofs/providers/halo2.ts +560 -0
  190. package/src/proofs/providers/index.ts +34 -0
  191. package/src/proofs/providers/kimchi.ts +641 -0
  192. package/src/proofs/validator.ts +881 -0
  193. package/src/proofs/verifier.ts +867 -0
  194. package/src/quantum/index.ts +112 -0
  195. package/src/quantum/winternitz-vault.ts +639 -0
  196. package/src/quantum/wots.ts +611 -0
  197. package/src/settlement/backends/direct-chain.ts +1 -0
  198. package/src/settlement/index.ts +9 -0
  199. package/src/settlement/router.ts +732 -46
  200. package/src/solana/index.ts +72 -0
  201. package/src/solana/jito-relayer.ts +687 -0
  202. package/src/solana/noir-verifier-types.ts +430 -0
  203. package/src/solana/noir-verifier.ts +816 -0
  204. package/src/stealth/address-derivation.ts +193 -0
  205. package/src/stealth/ed25519.ts +431 -0
  206. package/src/stealth/index.ts +233 -0
  207. package/src/stealth/meta-address.ts +221 -0
  208. package/src/stealth/secp256k1.ts +368 -0
  209. package/src/stealth/utils.ts +194 -0
  210. package/src/stealth.ts +50 -1504
  211. package/src/sync/index.ts +106 -0
  212. package/src/sync/manager.ts +504 -0
  213. package/src/sync/mock-provider.ts +318 -0
  214. package/src/sync/oblivious.ts +625 -0
  215. package/src/tokens/index.ts +15 -0
  216. package/src/tokens/registry.ts +301 -0
  217. package/src/utils/deprecation.ts +94 -0
  218. package/src/utils/index.ts +9 -0
  219. package/src/wallet/ethereum/index.ts +68 -0
  220. package/src/wallet/ethereum/metamask-privacy.ts +420 -0
  221. package/src/wallet/ethereum/multi-wallet.ts +646 -0
  222. package/src/wallet/ethereum/privacy-adapter.ts +700 -0
  223. package/src/wallet/ethereum/types.ts +3 -1
  224. package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
  225. package/src/wallet/hardware/index.ts +10 -0
  226. package/src/wallet/hardware/ledger-privacy.ts +414 -0
  227. package/src/wallet/index.ts +71 -0
  228. package/src/wallet/near/adapter.ts +626 -0
  229. package/src/wallet/near/index.ts +86 -0
  230. package/src/wallet/near/meteor-wallet.ts +1153 -0
  231. package/src/wallet/near/my-near-wallet.ts +790 -0
  232. package/src/wallet/near/wallet-selector.ts +702 -0
  233. package/src/wallet/solana/adapter.ts +6 -4
  234. package/src/wallet/solana/index.ts +13 -0
  235. package/src/wallet/solana/privacy-adapter.ts +567 -0
  236. package/src/wallet/sui/types.ts +6 -4
  237. package/src/zcash/rpc-client.ts +13 -6
  238. package/dist/chunk-2XIVXWHA.mjs +0 -1930
  239. package/dist/chunk-3INS3PR5.mjs +0 -884
  240. package/dist/chunk-3OVABDRH.mjs +0 -17096
  241. package/dist/chunk-7RFRWDCW.mjs +0 -1504
  242. package/dist/chunk-DLDWZFYC.mjs +0 -1495
  243. package/dist/chunk-E6SZWREQ.mjs +0 -57
  244. package/dist/chunk-F6F73W35.mjs +0 -16166
  245. package/dist/chunk-G33LB27A.mjs +0 -16166
  246. package/dist/chunk-HGU6HZRC.mjs +0 -231
  247. package/dist/chunk-L2K34JCU.mjs +0 -1496
  248. package/dist/chunk-OFDBEIEK.mjs +0 -16166
  249. package/dist/chunk-SF7YSLF5.mjs +0 -1515
  250. package/dist/chunk-SN4ZDTVW.mjs +0 -16166
  251. package/dist/chunk-WWUSGOXE.mjs +0 -17129
  252. package/dist/constants-VOI7BSLK.mjs +0 -27
  253. package/dist/index-B71aXVzk.d.ts +0 -13264
  254. package/dist/index-BYZbDjal.d.ts +0 -11390
  255. package/dist/index-CHB3KuOB.d.mts +0 -11859
  256. package/dist/index-CzWPI6Le.d.ts +0 -11859
  257. package/dist/index-pOIIuwfV.d.mts +0 -13264
  258. package/dist/index-xbWjohNq.d.mts +0 -11390
  259. package/dist/solana-4O4K45VU.mjs +0 -46
  260. package/dist/solana-5EMCTPTS.mjs +0 -46
  261. package/dist/solana-NDABAZ6P.mjs +0 -56
  262. package/dist/solana-Q4NAVBTS.mjs +0 -46
  263. package/dist/solana-ZYO63LY5.mjs +0 -46
@@ -0,0 +1,703 @@
1
+ /**
2
+ * Solana Stealth Address Scanner
3
+ *
4
+ * Advanced scanner for detecting incoming stealth payments with support for:
5
+ * - Real-time WebSocket subscriptions
6
+ * - Historical scanning with pagination
7
+ * - Batch scanning for multiple viewing keys
8
+ * - Efficient view tag filtering
9
+ *
10
+ * @module chains/solana/stealth-scanner
11
+ */
12
+
13
+ import { PublicKey, type Connection } from '@solana/web3.js'
14
+ // hexToBytes available but unused - kept for potential future view tag computation
15
+ // import { hexToBytes } from '@noble/hashes/utils'
16
+ import type { StealthAddress, HexString } from '@sip-protocol/types'
17
+ import { checkEd25519StealthAddress, solanaAddressToEd25519PublicKey } from '../../stealth'
18
+ import { parseAnnouncement, type SolanaAnnouncement } from './types'
19
+ import {
20
+ SIP_MEMO_PREFIX,
21
+ MEMO_PROGRAM_ID,
22
+ DEFAULT_SCAN_LIMIT,
23
+ VIEW_TAG_MAX,
24
+ } from './constants'
25
+ import { getTokenSymbol, parseTokenTransferFromBalances } from './utils'
26
+ import type { SolanaRPCProvider } from './providers/interface'
27
+
28
+ // ─── Types ────────────────────────────────────────────────────────────────────
29
+
30
+ /**
31
+ * A recipient to scan for (viewing + spending key pair)
32
+ */
33
+ export interface ScanRecipient {
34
+ /**
35
+ * Viewing private key (hex)
36
+ * @security SENSITIVE - enables scanning for payments
37
+ */
38
+ viewingPrivateKey: HexString
39
+
40
+ /**
41
+ * Spending public key (hex)
42
+ */
43
+ spendingPublicKey: HexString
44
+
45
+ /**
46
+ * Optional label for this recipient
47
+ */
48
+ label?: string
49
+ }
50
+
51
+ /**
52
+ * Options for the stealth scanner
53
+ */
54
+ export interface StealthScannerOptions {
55
+ /**
56
+ * Solana RPC connection
57
+ */
58
+ connection: Connection
59
+
60
+ /**
61
+ * Optional RPC provider for efficient queries
62
+ */
63
+ provider?: SolanaRPCProvider
64
+
65
+ /**
66
+ * Maximum results per scan batch
67
+ * @default 100
68
+ */
69
+ batchSize?: number
70
+
71
+ /**
72
+ * Enable view tag filtering for efficient scanning
73
+ * @default true
74
+ */
75
+ useViewTagFilter?: boolean
76
+ }
77
+
78
+ /**
79
+ * Options for historical scanning
80
+ */
81
+ export interface HistoricalScanOptions {
82
+ /**
83
+ * Start slot for scanning
84
+ */
85
+ fromSlot?: number
86
+
87
+ /**
88
+ * End slot for scanning
89
+ */
90
+ toSlot?: number
91
+
92
+ /**
93
+ * Maximum number of transactions to scan
94
+ * @default 1000
95
+ */
96
+ limit?: number
97
+
98
+ /**
99
+ * Cursor for pagination (signature of last scanned tx)
100
+ */
101
+ beforeSignature?: string
102
+ }
103
+
104
+ /**
105
+ * A detected stealth payment
106
+ */
107
+ export interface DetectedPayment {
108
+ /**
109
+ * Stealth address that received the payment (base58)
110
+ */
111
+ stealthAddress: string
112
+
113
+ /**
114
+ * Ephemeral public key from the sender (base58)
115
+ */
116
+ ephemeralPublicKey: string
117
+
118
+ /**
119
+ * View tag for efficient scanning
120
+ */
121
+ viewTag: number
122
+
123
+ /**
124
+ * Token amount (in smallest unit)
125
+ */
126
+ amount: bigint
127
+
128
+ /**
129
+ * Token mint address (base58)
130
+ */
131
+ mint: string
132
+
133
+ /**
134
+ * Human-readable token symbol
135
+ */
136
+ tokenSymbol: string
137
+
138
+ /**
139
+ * Transaction signature
140
+ */
141
+ txSignature: string
142
+
143
+ /**
144
+ * Slot number
145
+ */
146
+ slot: number
147
+
148
+ /**
149
+ * Unix timestamp
150
+ */
151
+ timestamp: number
152
+
153
+ /**
154
+ * Label of the recipient this payment was detected for
155
+ */
156
+ recipientLabel?: string
157
+ }
158
+
159
+ /**
160
+ * Result of a historical scan
161
+ */
162
+ export interface HistoricalScanResult {
163
+ /**
164
+ * Detected payments
165
+ */
166
+ payments: DetectedPayment[]
167
+
168
+ /**
169
+ * Total transactions scanned
170
+ */
171
+ scannedCount: number
172
+
173
+ /**
174
+ * Whether more results are available
175
+ */
176
+ hasMore: boolean
177
+
178
+ /**
179
+ * Cursor for next page (last signature scanned)
180
+ */
181
+ nextCursor?: string
182
+
183
+ /**
184
+ * Last slot scanned
185
+ */
186
+ lastSlot?: number
187
+ }
188
+
189
+ /**
190
+ * Callback for real-time payment detection
191
+ */
192
+ export type PaymentCallback = (payment: DetectedPayment) => void
193
+
194
+ /**
195
+ * Callback for scan errors
196
+ */
197
+ export type ErrorCallback = (error: Error) => void
198
+
199
+ // ─── View Tag Computation ─────────────────────────────────────────────────────
200
+ // NOTE: View tag filtering optimization is planned for future implementation.
201
+ // The view tag is derived from the shared secret between ephemeral and viewing keys,
202
+ // which allows for efficient filtering without full ECDH computation.
203
+ // Currently all announcements are checked against all recipients.
204
+
205
+ // ─── StealthScanner Class ─────────────────────────────────────────────────────
206
+
207
+ /**
208
+ * Advanced stealth address scanner
209
+ *
210
+ * Provides efficient scanning for incoming stealth payments with support for
211
+ * multiple recipients, real-time subscriptions, and pagination.
212
+ *
213
+ * @example Basic usage
214
+ * ```typescript
215
+ * const scanner = new StealthScanner({
216
+ * connection,
217
+ * provider: heliusProvider,
218
+ * })
219
+ *
220
+ * // Add recipients to scan for
221
+ * scanner.addRecipient({
222
+ * viewingPrivateKey: '0x...',
223
+ * spendingPublicKey: '0x...',
224
+ * label: 'Wallet 1',
225
+ * })
226
+ *
227
+ * // Historical scan
228
+ * const result = await scanner.scanHistorical({
229
+ * fromSlot: 250000000,
230
+ * limit: 1000,
231
+ * })
232
+ *
233
+ * console.log(`Found ${result.payments.length} payments`)
234
+ * ```
235
+ *
236
+ * @example Real-time scanning
237
+ * ```typescript
238
+ * scanner.subscribe(
239
+ * (payment) => console.log('New payment:', payment),
240
+ * (error) => console.error('Scan error:', error)
241
+ * )
242
+ *
243
+ * // Later: stop subscription
244
+ * scanner.unsubscribe()
245
+ * ```
246
+ */
247
+ export class StealthScanner {
248
+ private connection: Connection
249
+ private provider?: SolanaRPCProvider
250
+ private recipients: ScanRecipient[] = []
251
+ private batchSize: number
252
+ private subscriptionId: number | null = null
253
+ private paymentCallback: PaymentCallback | null = null
254
+ private errorCallback: ErrorCallback | null = null
255
+
256
+ constructor(options: StealthScannerOptions) {
257
+ this.connection = options.connection
258
+ this.provider = options.provider
259
+ this.batchSize = options.batchSize ?? DEFAULT_SCAN_LIMIT
260
+ // Note: useViewTagFilter option is accepted for future optimization
261
+ // Currently all announcements are checked against all recipients
262
+ void options.useViewTagFilter
263
+ }
264
+
265
+ /**
266
+ * Add a recipient to scan for
267
+ *
268
+ * @param recipient - Recipient with viewing/spending keys
269
+ */
270
+ addRecipient(recipient: ScanRecipient): void {
271
+ this.recipients.push(recipient)
272
+ }
273
+
274
+ /**
275
+ * Remove a recipient by label
276
+ *
277
+ * @param label - Recipient label to remove
278
+ */
279
+ removeRecipient(label: string): void {
280
+ this.recipients = this.recipients.filter(r => r.label !== label)
281
+ }
282
+
283
+ /**
284
+ * Clear all recipients
285
+ */
286
+ clearRecipients(): void {
287
+ this.recipients = []
288
+ }
289
+
290
+ /**
291
+ * Get current recipients
292
+ */
293
+ getRecipients(): ScanRecipient[] {
294
+ return [...this.recipients]
295
+ }
296
+
297
+ /**
298
+ * Scan historical transactions for stealth payments
299
+ *
300
+ * @param options - Scan options
301
+ * @returns Scan result with detected payments
302
+ */
303
+ async scanHistorical(options: HistoricalScanOptions = {}): Promise<HistoricalScanResult> {
304
+ if (this.recipients.length === 0) {
305
+ return {
306
+ payments: [],
307
+ scannedCount: 0,
308
+ hasMore: false,
309
+ }
310
+ }
311
+
312
+ const {
313
+ fromSlot,
314
+ toSlot,
315
+ limit = 1000,
316
+ beforeSignature,
317
+ } = options
318
+
319
+ const payments: DetectedPayment[] = []
320
+ let scannedCount = 0
321
+ let lastSignature: string | undefined
322
+ let lastSlot: number | undefined
323
+
324
+ const memoProgram = new PublicKey(MEMO_PROGRAM_ID)
325
+
326
+ try {
327
+ // Get transaction signatures
328
+ const signatures = await this.connection.getSignaturesForAddress(
329
+ memoProgram,
330
+ {
331
+ limit: Math.min(limit, this.batchSize),
332
+ before: beforeSignature,
333
+ minContextSlot: fromSlot,
334
+ }
335
+ )
336
+
337
+ // Filter by slot range
338
+ const filteredSignatures = toSlot
339
+ ? signatures.filter(s => s.slot <= toSlot)
340
+ : signatures
341
+
342
+ // Process each transaction
343
+ for (const sigInfo of filteredSignatures) {
344
+ scannedCount++
345
+ lastSignature = sigInfo.signature
346
+ lastSlot = sigInfo.slot
347
+
348
+ try {
349
+ const tx = await this.connection.getTransaction(sigInfo.signature, {
350
+ maxSupportedTransactionVersion: 0,
351
+ })
352
+
353
+ if (!tx?.meta?.logMessages) continue
354
+
355
+ // Look for SIP announcements in logs
356
+ for (const log of tx.meta.logMessages) {
357
+ if (!log.includes(SIP_MEMO_PREFIX)) continue
358
+
359
+ const memoMatch = log.match(/Program log: (.+)/)
360
+ if (!memoMatch) continue
361
+
362
+ const announcement = parseAnnouncement(memoMatch[1])
363
+ if (!announcement) continue
364
+
365
+ // Check against all recipients
366
+ const detectedPayment = await this.checkAnnouncementAgainstRecipients(
367
+ announcement,
368
+ { signature: sigInfo.signature, slot: sigInfo.slot, blockTime: sigInfo.blockTime ?? null },
369
+ tx.meta.preTokenBalances as any,
370
+ tx.meta.postTokenBalances as any
371
+ )
372
+
373
+ if (detectedPayment) {
374
+ payments.push(detectedPayment)
375
+ }
376
+ }
377
+ } catch {
378
+ // Skip failed transaction parsing
379
+ }
380
+ }
381
+
382
+ return {
383
+ payments,
384
+ scannedCount,
385
+ hasMore: signatures.length >= Math.min(limit, this.batchSize),
386
+ nextCursor: lastSignature,
387
+ lastSlot,
388
+ }
389
+ } catch (err) {
390
+ const message = err instanceof Error ? err.message : String(err)
391
+ throw new Error(`Historical scan failed: ${message}`)
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Subscribe to real-time stealth payments
397
+ *
398
+ * Uses WebSocket to monitor new transactions for stealth payments.
399
+ *
400
+ * @param onPayment - Callback when a payment is detected
401
+ * @param onError - Callback when an error occurs
402
+ */
403
+ subscribe(onPayment: PaymentCallback, onError?: ErrorCallback): void {
404
+ if (this.subscriptionId !== null) {
405
+ throw new Error('Already subscribed. Call unsubscribe() first.')
406
+ }
407
+
408
+ if (this.recipients.length === 0) {
409
+ throw new Error('No recipients configured. Call addRecipient() first.')
410
+ }
411
+
412
+ this.paymentCallback = onPayment
413
+ this.errorCallback = onError ?? null
414
+
415
+ const memoProgram = new PublicKey(MEMO_PROGRAM_ID)
416
+
417
+ // Subscribe to logs mentioning the memo program
418
+ this.subscriptionId = this.connection.onLogs(
419
+ memoProgram,
420
+ async (logs) => {
421
+ try {
422
+ // Look for SIP announcements
423
+ for (const log of logs.logs) {
424
+ if (!log.includes(SIP_MEMO_PREFIX)) continue
425
+
426
+ const memoMatch = log.match(/Program log: (.+)/)
427
+ if (!memoMatch) continue
428
+
429
+ const announcement = parseAnnouncement(memoMatch[1])
430
+ if (!announcement) continue
431
+
432
+ // Get full transaction for balance info
433
+ const tx = await this.connection.getTransaction(logs.signature, {
434
+ maxSupportedTransactionVersion: 0,
435
+ })
436
+
437
+ if (!tx?.meta) continue
438
+
439
+ const payment = await this.checkAnnouncementAgainstRecipients(
440
+ announcement,
441
+ { signature: logs.signature, slot: tx.slot ?? 0, blockTime: tx.blockTime ?? null },
442
+ tx.meta.preTokenBalances as any,
443
+ tx.meta.postTokenBalances as any
444
+ )
445
+
446
+ if (payment && this.paymentCallback) {
447
+ this.paymentCallback(payment)
448
+ }
449
+ }
450
+ } catch (err) {
451
+ if (this.errorCallback) {
452
+ this.errorCallback(err instanceof Error ? err : new Error(String(err)))
453
+ }
454
+ }
455
+ },
456
+ 'confirmed'
457
+ )
458
+ }
459
+
460
+ /**
461
+ * Unsubscribe from real-time payments
462
+ */
463
+ async unsubscribe(): Promise<void> {
464
+ if (this.subscriptionId !== null) {
465
+ await this.connection.removeOnLogsListener(this.subscriptionId)
466
+ this.subscriptionId = null
467
+ this.paymentCallback = null
468
+ this.errorCallback = null
469
+ }
470
+ }
471
+
472
+ /**
473
+ * Check if currently subscribed
474
+ */
475
+ isSubscribed(): boolean {
476
+ return this.subscriptionId !== null
477
+ }
478
+
479
+ /**
480
+ * Check an announcement against all recipients
481
+ */
482
+ private async checkAnnouncementAgainstRecipients(
483
+ announcement: SolanaAnnouncement,
484
+ sigInfo: { signature: string; slot: number; blockTime: number | null },
485
+ preBalances: Parameters<typeof parseTokenTransferFromBalances>[0],
486
+ postBalances: Parameters<typeof parseTokenTransferFromBalances>[1]
487
+ ): Promise<DetectedPayment | null> {
488
+ // Parse view tag
489
+ const viewTagNumber = parseInt(announcement.viewTag, 16)
490
+ if (!Number.isInteger(viewTagNumber) || viewTagNumber < 0 || viewTagNumber > VIEW_TAG_MAX) {
491
+ return null
492
+ }
493
+
494
+ // Convert ephemeral key
495
+ let ephemeralPubKeyHex: HexString
496
+ try {
497
+ ephemeralPubKeyHex = solanaAddressToEd25519PublicKey(announcement.ephemeralPublicKey)
498
+ } catch {
499
+ return null
500
+ }
501
+
502
+ // Convert stealth address
503
+ let stealthAddressHex: HexString
504
+ try {
505
+ stealthAddressHex = announcement.stealthAddress
506
+ ? solanaAddressToEd25519PublicKey(announcement.stealthAddress)
507
+ : ('0x' + '00'.repeat(32)) as HexString
508
+ } catch {
509
+ return null
510
+ }
511
+
512
+ const stealthAddressToCheck: StealthAddress = {
513
+ address: stealthAddressHex,
514
+ ephemeralPublicKey: ephemeralPubKeyHex,
515
+ viewTag: viewTagNumber,
516
+ }
517
+
518
+ // Check against each recipient
519
+ for (const recipient of this.recipients) {
520
+ try {
521
+ const isMatch = checkEd25519StealthAddress(
522
+ stealthAddressToCheck,
523
+ recipient.viewingPrivateKey,
524
+ recipient.spendingPublicKey
525
+ )
526
+
527
+ if (isMatch) {
528
+ // Parse token transfer
529
+ const transferInfo = parseTokenTransferFromBalances(preBalances, postBalances)
530
+ if (!transferInfo) continue
531
+
532
+ // Get current balance if provider available
533
+ let amount = transferInfo.amount
534
+ if (this.provider && announcement.stealthAddress) {
535
+ try {
536
+ const balance = await this.provider.getTokenBalance(
537
+ announcement.stealthAddress,
538
+ transferInfo.mint
539
+ )
540
+ if (balance > 0n) {
541
+ amount = balance
542
+ }
543
+ } catch {
544
+ // Use parsed amount
545
+ }
546
+ }
547
+
548
+ return {
549
+ stealthAddress: announcement.stealthAddress || '',
550
+ ephemeralPublicKey: announcement.ephemeralPublicKey,
551
+ viewTag: viewTagNumber,
552
+ amount,
553
+ mint: transferInfo.mint,
554
+ tokenSymbol: getTokenSymbol(transferInfo.mint) || 'UNKNOWN',
555
+ txSignature: sigInfo.signature,
556
+ slot: sigInfo.slot,
557
+ timestamp: sigInfo.blockTime || 0,
558
+ recipientLabel: recipient.label,
559
+ }
560
+ }
561
+ } catch {
562
+ // Invalid keys or malformed data, try next recipient
563
+ }
564
+ }
565
+
566
+ return null
567
+ }
568
+ }
569
+
570
+ // ─── Factory Function ─────────────────────────────────────────────────────────
571
+
572
+ /**
573
+ * Create a new stealth scanner
574
+ *
575
+ * @param options - Scanner options
576
+ * @returns Configured stealth scanner
577
+ *
578
+ * @example
579
+ * ```typescript
580
+ * const scanner = createStealthScanner({
581
+ * connection,
582
+ * provider: heliusProvider,
583
+ * })
584
+ * ```
585
+ */
586
+ export function createStealthScanner(options: StealthScannerOptions): StealthScanner {
587
+ return new StealthScanner(options)
588
+ }
589
+
590
+ // ─── Batch Scanning Utilities ─────────────────────────────────────────────────
591
+
592
+ /**
593
+ * Batch scan for multiple recipients across a slot range
594
+ *
595
+ * Efficiently scans a range of slots for payments to multiple recipients.
596
+ * Use this for initial wallet sync or periodic historical scans.
597
+ *
598
+ * @param options - Scanner options
599
+ * @param recipients - Recipients to scan for
600
+ * @param scanOptions - Historical scan options
601
+ * @returns All detected payments grouped by recipient
602
+ *
603
+ * @example
604
+ * ```typescript
605
+ * const results = await batchScanForRecipients(
606
+ * { connection, provider },
607
+ * [
608
+ * { viewingPrivateKey: '0x...', spendingPublicKey: '0x...', label: 'Wallet 1' },
609
+ * { viewingPrivateKey: '0x...', spendingPublicKey: '0x...', label: 'Wallet 2' },
610
+ * ],
611
+ * { fromSlot: 250000000, limit: 5000 }
612
+ * )
613
+ *
614
+ * for (const [label, payments] of Object.entries(results)) {
615
+ * console.log(`${label}: ${payments.length} payments`)
616
+ * }
617
+ * ```
618
+ */
619
+ export async function batchScanForRecipients(
620
+ options: StealthScannerOptions,
621
+ recipients: ScanRecipient[],
622
+ scanOptions: HistoricalScanOptions = {}
623
+ ): Promise<Record<string, DetectedPayment[]>> {
624
+ const scanner = createStealthScanner(options)
625
+
626
+ for (const recipient of recipients) {
627
+ scanner.addRecipient(recipient)
628
+ }
629
+
630
+ const result = await scanner.scanHistorical(scanOptions)
631
+
632
+ // Group by recipient label
633
+ const grouped: Record<string, DetectedPayment[]> = {}
634
+
635
+ for (const recipient of recipients) {
636
+ const label = recipient.label || 'unknown'
637
+ grouped[label] = result.payments.filter(p => p.recipientLabel === label)
638
+ }
639
+
640
+ return grouped
641
+ }
642
+
643
+ /**
644
+ * Full historical scan with automatic pagination
645
+ *
646
+ * Scans the entire history (or specified range) with automatic pagination.
647
+ * Useful for complete wallet sync.
648
+ *
649
+ * @param options - Scanner options
650
+ * @param recipients - Recipients to scan for
651
+ * @param scanOptions - Historical scan options
652
+ * @param onProgress - Optional progress callback
653
+ * @returns All detected payments
654
+ *
655
+ * @example
656
+ * ```typescript
657
+ * const payments = await fullHistoricalScan(
658
+ * { connection },
659
+ * [{ viewingPrivateKey, spendingPublicKey }],
660
+ * { fromSlot: 250000000 },
661
+ * (scanned, found) => console.log(`Scanned ${scanned}, found ${found}`)
662
+ * )
663
+ * ```
664
+ */
665
+ export async function fullHistoricalScan(
666
+ options: StealthScannerOptions,
667
+ recipients: ScanRecipient[],
668
+ scanOptions: HistoricalScanOptions = {},
669
+ onProgress?: (scannedCount: number, foundCount: number) => void
670
+ ): Promise<DetectedPayment[]> {
671
+ const scanner = createStealthScanner(options)
672
+
673
+ for (const recipient of recipients) {
674
+ scanner.addRecipient(recipient)
675
+ }
676
+
677
+ const allPayments: DetectedPayment[] = []
678
+ let cursor: string | undefined = scanOptions.beforeSignature
679
+ let totalScanned = 0
680
+ const maxIterations = 100 // Safety limit
681
+
682
+ for (let i = 0; i < maxIterations; i++) {
683
+ const result = await scanner.scanHistorical({
684
+ ...scanOptions,
685
+ beforeSignature: cursor,
686
+ })
687
+
688
+ allPayments.push(...result.payments)
689
+ totalScanned += result.scannedCount
690
+
691
+ if (onProgress) {
692
+ onProgress(totalScanned, allPayments.length)
693
+ }
694
+
695
+ if (!result.hasMore || !result.nextCursor) {
696
+ break
697
+ }
698
+
699
+ cursor = result.nextCursor
700
+ }
701
+
702
+ return allPayments
703
+ }