@sip-protocol/sdk 0.7.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/browser.d.mts +1 -1
  2. package/dist/browser.d.ts +1 -1
  3. package/dist/browser.js +2926 -341
  4. package/dist/browser.mjs +48 -2
  5. package/dist/chunk-2XIVXWHA.mjs +1930 -0
  6. package/dist/chunk-3M3HNQCW.mjs +18253 -0
  7. package/dist/chunk-7RFRWDCW.mjs +1504 -0
  8. package/dist/chunk-F6F73W35.mjs +16166 -0
  9. package/dist/chunk-OFDBEIEK.mjs +16166 -0
  10. package/dist/chunk-SF7YSLF5.mjs +1515 -0
  11. package/dist/chunk-WWUSGOXE.mjs +17129 -0
  12. package/dist/index-8MQz13eJ.d.mts +13746 -0
  13. package/dist/index-B71aXVzk.d.ts +13264 -0
  14. package/dist/index-DIBZHOOQ.d.ts +13746 -0
  15. package/dist/index-pOIIuwfV.d.mts +13264 -0
  16. package/dist/index.d.mts +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +2911 -326
  19. package/dist/index.mjs +48 -2
  20. package/dist/solana-4O4K45VU.mjs +46 -0
  21. package/dist/solana-NDABAZ6P.mjs +56 -0
  22. package/dist/solana-ZYO63LY5.mjs +46 -0
  23. package/package.json +2 -2
  24. package/src/chains/solana/index.ts +24 -0
  25. package/src/chains/solana/providers/generic.ts +160 -0
  26. package/src/chains/solana/providers/helius.ts +249 -0
  27. package/src/chains/solana/providers/index.ts +54 -0
  28. package/src/chains/solana/providers/interface.ts +178 -0
  29. package/src/chains/solana/providers/webhook.ts +519 -0
  30. package/src/chains/solana/scan.ts +88 -8
  31. package/src/chains/solana/types.ts +20 -1
  32. package/src/compliance/index.ts +14 -0
  33. package/src/compliance/range-sas.ts +591 -0
  34. package/src/index.ts +99 -0
  35. package/src/privacy-backends/index.ts +86 -0
  36. package/src/privacy-backends/interface.ts +263 -0
  37. package/src/privacy-backends/privacycash-types.ts +278 -0
  38. package/src/privacy-backends/privacycash.ts +460 -0
  39. package/src/privacy-backends/registry.ts +278 -0
  40. package/src/privacy-backends/router.ts +346 -0
  41. package/src/privacy-backends/sip-native.ts +253 -0
  42. package/src/proofs/noir.ts +1 -1
  43. package/src/surveillance/algorithms/address-reuse.ts +143 -0
  44. package/src/surveillance/algorithms/cluster.ts +247 -0
  45. package/src/surveillance/algorithms/exchange.ts +295 -0
  46. package/src/surveillance/algorithms/temporal.ts +337 -0
  47. package/src/surveillance/analyzer.ts +442 -0
  48. package/src/surveillance/index.ts +64 -0
  49. package/src/surveillance/scoring.ts +372 -0
  50. package/src/surveillance/types.ts +264 -0
@@ -0,0 +1,460 @@
1
+ /**
2
+ * PrivacyCash Privacy Backend
3
+ *
4
+ * Implements the PrivacyBackend interface using PrivacyCash pool mixing.
5
+ * PrivacyCash is a Tornado Cash-style mixer for Solana that provides
6
+ * anonymity through fixed-size deposit pools.
7
+ *
8
+ * ## Key Characteristics
9
+ *
10
+ * - **Pool Mixing**: Users deposit into fixed-size pools and withdraw to fresh addresses
11
+ * - **Anonymity Set**: Privacy comes from the number of depositors in each pool
12
+ * - **Fixed Amounts**: Only supports specific pool sizes (0.1, 1, 10, 100 SOL)
13
+ * - **No Compliance**: Does not support viewing keys or selective disclosure
14
+ *
15
+ * ## Trade-offs vs SIP Native
16
+ *
17
+ * | Feature | PrivacyCash | SIP Native |
18
+ * |---------|-------------|------------|
19
+ * | Amount hidden | No (fixed pools) | Yes (Pedersen) |
20
+ * | Sender hidden | Yes (pool mixing) | Yes (stealth) |
21
+ * | Compliance | No | Yes (viewing keys) |
22
+ * | Anonymity set | Yes (pool size) | No |
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * import { PrivacyCashBackend, PrivacyBackendRegistry } from '@sip-protocol/sdk'
27
+ *
28
+ * const backend = new PrivacyCashBackend({
29
+ * rpcUrl: 'https://api.mainnet-beta.solana.com',
30
+ * })
31
+ *
32
+ * const registry = new PrivacyBackendRegistry()
33
+ * registry.register(backend, { priority: 80 })
34
+ *
35
+ * // Check if available for 1 SOL transfer
36
+ * const availability = await backend.checkAvailability({
37
+ * chain: 'solana',
38
+ * sender: '...',
39
+ * recipient: '...',
40
+ * mint: null,
41
+ * amount: BigInt(1_000_000_000), // 1 SOL
42
+ * decimals: 9,
43
+ * })
44
+ *
45
+ * if (availability.available) {
46
+ * console.log(`Estimated cost: ${availability.estimatedCost}`)
47
+ * }
48
+ * ```
49
+ *
50
+ * @see https://github.com/Privacy-Cash/privacy-cash-sdk
51
+ */
52
+
53
+ import type {
54
+ PrivacyBackend,
55
+ BackendType,
56
+ BackendCapabilities,
57
+ TransferParams,
58
+ TransactionResult,
59
+ AvailabilityResult,
60
+ } from './interface'
61
+
62
+ import {
63
+ SOL_POOL_AMOUNTS,
64
+ SPL_TOKEN_MINTS,
65
+ findNearestPoolSize,
66
+ isValidPoolAmount,
67
+ getAvailablePoolSizes,
68
+ type PrivacyCashSPLToken,
69
+ type PoolInfo,
70
+ type IPrivacyCashSDK,
71
+ } from './privacycash-types'
72
+
73
+ /**
74
+ * Default estimated anonymity set size
75
+ * In production, this would be fetched from the pool
76
+ */
77
+ const DEFAULT_ANONYMITY_SET = 50
78
+
79
+ /**
80
+ * Estimated time for pool operations (deposit + withdrawal)
81
+ * Pool mixing typically requires waiting for more deposits
82
+ */
83
+ const ESTIMATED_TIME_MS = 30000 // 30 seconds minimum
84
+
85
+ /**
86
+ * Base cost for PrivacyCash operations (in lamports)
87
+ * Includes: deposit tx + withdrawal tx + relayer fee
88
+ */
89
+ const BASE_COST_LAMPORTS = BigInt(10_000_000) // ~0.01 SOL
90
+
91
+ /**
92
+ * Configuration options for PrivacyCash backend
93
+ */
94
+ export interface PrivacyCashBackendConfig {
95
+ /** Solana RPC endpoint URL */
96
+ rpcUrl?: string
97
+ /** Network type */
98
+ network?: 'mainnet-beta' | 'devnet'
99
+ /** Relayer URL for withdrawals */
100
+ relayerUrl?: string
101
+ /** Minimum anonymity set required for withdrawals */
102
+ minAnonymitySet?: number
103
+ /** Custom SDK instance (for testing) */
104
+ sdk?: IPrivacyCashSDK
105
+ }
106
+
107
+ /**
108
+ * PrivacyCash capabilities (static)
109
+ */
110
+ const PRIVACYCASH_CAPABILITIES: BackendCapabilities = {
111
+ hiddenAmount: false, // Fixed pool sizes, amount is known
112
+ hiddenSender: true, // Pool mixing hides sender
113
+ hiddenRecipient: true, // Withdrawal to fresh address
114
+ hiddenCompute: false, // No compute privacy
115
+ complianceSupport: false, // No viewing keys
116
+ anonymitySet: DEFAULT_ANONYMITY_SET,
117
+ setupRequired: false, // No setup needed
118
+ latencyEstimate: 'medium', // Need to wait for pool
119
+ supportedTokens: 'all', // SOL + USDC/USDT
120
+ minAmount: SOL_POOL_AMOUNTS[0], // Smallest pool
121
+ maxAmount: SOL_POOL_AMOUNTS[SOL_POOL_AMOUNTS.length - 1], // Largest pool
122
+ }
123
+
124
+ /**
125
+ * PrivacyCash Privacy Backend
126
+ *
127
+ * Uses pool mixing for transaction privacy on Solana.
128
+ * Integrates with the PrivacyCash protocol as an alternative
129
+ * to SIP Native's cryptographic approach.
130
+ */
131
+ export class PrivacyCashBackend implements PrivacyBackend {
132
+ readonly name = 'privacycash'
133
+ readonly type: BackendType = 'transaction'
134
+ readonly chains: string[] = ['solana']
135
+
136
+ private config: Required<Omit<PrivacyCashBackendConfig, 'sdk'>> & {
137
+ sdk?: IPrivacyCashSDK
138
+ }
139
+ private poolCache: Map<string, PoolInfo> = new Map()
140
+ private poolCacheExpiry: Map<string, number> = new Map()
141
+
142
+ /**
143
+ * Create a new PrivacyCash backend
144
+ *
145
+ * @param config - Backend configuration
146
+ */
147
+ constructor(config: PrivacyCashBackendConfig = {}) {
148
+ this.config = {
149
+ rpcUrl: config.rpcUrl ?? 'https://api.mainnet-beta.solana.com',
150
+ network: config.network ?? 'mainnet-beta',
151
+ relayerUrl: config.relayerUrl ?? 'https://relayer.privacycash.org',
152
+ minAnonymitySet: config.minAnonymitySet ?? 5,
153
+ sdk: config.sdk,
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Check if backend is available for given parameters
159
+ */
160
+ async checkAvailability(params: TransferParams): Promise<AvailabilityResult> {
161
+ // Validate amount is non-negative
162
+ if (params.amount < BigInt(0)) {
163
+ return {
164
+ available: false,
165
+ reason: 'Amount cannot be negative',
166
+ }
167
+ }
168
+
169
+ // Only supports Solana
170
+ if (params.chain !== 'solana') {
171
+ return {
172
+ available: false,
173
+ reason: `PrivacyCash only supports Solana, not '${params.chain}'`,
174
+ }
175
+ }
176
+
177
+ // Determine if this is SOL or SPL token
178
+ const isSOL = params.mint === null
179
+ const isSupportedSPL = params.mint
180
+ ? this.isSupportedSPLToken(params.mint)
181
+ : false
182
+
183
+ if (!isSOL && !isSupportedSPL) {
184
+ return {
185
+ available: false,
186
+ reason: params.mint
187
+ ? `Token ${params.mint} not supported. PrivacyCash supports SOL, USDC, and USDT only.`
188
+ : 'Invalid token configuration',
189
+ }
190
+ }
191
+
192
+ // Check if amount matches a pool size
193
+ if (!isValidPoolAmount(params.amount, isSOL)) {
194
+ const nearestPool = findNearestPoolSize(params.amount, isSOL)
195
+ const availablePools = getAvailablePoolSizes(isSOL)
196
+ .map(p => this.formatAmount(p, params.decimals))
197
+ .join(', ')
198
+
199
+ return {
200
+ available: false,
201
+ reason:
202
+ `Amount must match a pool size. ` +
203
+ `Nearest pool: ${this.formatAmount(nearestPool, params.decimals)}. ` +
204
+ `Available pools: ${availablePools}`,
205
+ }
206
+ }
207
+
208
+ // Check pool liquidity (in production, query actual pool)
209
+ const poolInfo = await this.getPoolInfo(params.amount, isSOL ? undefined : this.getSPLToken(params.mint!))
210
+
211
+ if (poolInfo.depositors < this.config.minAnonymitySet) {
212
+ return {
213
+ available: false,
214
+ reason:
215
+ `Pool anonymity set (${poolInfo.depositors}) below minimum (${this.config.minAnonymitySet}). ` +
216
+ `Wait for more deposits or use a different pool.`,
217
+ }
218
+ }
219
+
220
+ // Viewing keys are not supported - warn if provided
221
+ if (params.viewingKey) {
222
+ console.warn(
223
+ '[PrivacyCash] Viewing keys are not supported. ' +
224
+ 'Use SIP Native backend for compliance features.'
225
+ )
226
+ }
227
+
228
+ return {
229
+ available: true,
230
+ estimatedCost: this.estimateCostForTransfer(params),
231
+ estimatedTime: ESTIMATED_TIME_MS,
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Get backend capabilities
237
+ */
238
+ getCapabilities(): BackendCapabilities {
239
+ return {
240
+ ...PRIVACYCASH_CAPABILITIES,
241
+ // Dynamic anonymity set from cache if available
242
+ anonymitySet: this.getAverageAnonymitySet(),
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Execute a privacy-preserving transfer via pool mixing
248
+ *
249
+ * This performs a two-step process:
250
+ * 1. Deposit into the matching pool (returns a secret note)
251
+ * 2. Withdraw to recipient address (using the note)
252
+ *
253
+ * For production use, the note should be stored securely
254
+ * and withdrawal can happen later.
255
+ */
256
+ async execute(params: TransferParams): Promise<TransactionResult> {
257
+ // Validate availability first
258
+ const availability = await this.checkAvailability(params)
259
+ if (!availability.available) {
260
+ return {
261
+ success: false,
262
+ error: availability.reason,
263
+ backend: this.name,
264
+ }
265
+ }
266
+
267
+ try {
268
+ const isSOL = params.mint === null
269
+
270
+ // In a real implementation with the SDK:
271
+ // 1. const depositResult = await this.sdk.deposit({ amount, wallet })
272
+ // 2. Store depositResult.note securely
273
+ // 3. const withdrawResult = await this.sdk.withdraw({ amount, recipient, note })
274
+
275
+ // Simulated result for now (SDK requires Node 24+)
276
+ const simulatedNote = this.generateSimulatedNote()
277
+ const simulatedSignature = `pc_${Date.now()}_${Math.random().toString(36).slice(2)}`
278
+
279
+ return {
280
+ success: true,
281
+ signature: simulatedSignature,
282
+ backend: this.name,
283
+ metadata: {
284
+ poolSize: params.amount.toString(),
285
+ isSOL,
286
+ token: isSOL ? 'SOL' : this.getSPLToken(params.mint!),
287
+ note: simulatedNote, // In production, this is the withdrawal proof
288
+ anonymitySet: await this.getPoolAnonymitySet(params.amount, isSOL, params.mint),
289
+ timestamp: Date.now(),
290
+ warning: 'Simulated result - SDK integration pending',
291
+ },
292
+ }
293
+ } catch (error) {
294
+ return {
295
+ success: false,
296
+ error: error instanceof Error ? error.message : 'Unknown error',
297
+ backend: this.name,
298
+ }
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Estimate cost for a transfer
304
+ */
305
+ async estimateCost(params: TransferParams): Promise<bigint> {
306
+ return this.estimateCostForTransfer(params)
307
+ }
308
+
309
+ // ─── Private Methods ─────────────────────────────────────────────────────────
310
+
311
+ /**
312
+ * Check if a mint address is a supported SPL token
313
+ */
314
+ private isSupportedSPLToken(mint: string): boolean {
315
+ return Object.values(SPL_TOKEN_MINTS).includes(mint)
316
+ }
317
+
318
+ /**
319
+ * Get SPL token type from mint address
320
+ */
321
+ private getSPLToken(mint: string): PrivacyCashSPLToken | undefined {
322
+ for (const [token, address] of Object.entries(SPL_TOKEN_MINTS)) {
323
+ if (address === mint) {
324
+ return token as PrivacyCashSPLToken
325
+ }
326
+ }
327
+ return undefined
328
+ }
329
+
330
+ /**
331
+ * Format amount for display
332
+ */
333
+ private formatAmount(amount: bigint, decimals: number): string {
334
+ const divisor = BigInt(10 ** decimals)
335
+ const whole = amount / divisor
336
+ const fraction = amount % divisor
337
+ if (fraction === BigInt(0)) {
338
+ return whole.toString()
339
+ }
340
+ return `${whole}.${fraction.toString().padStart(decimals, '0').replace(/0+$/, '')}`
341
+ }
342
+
343
+ /**
344
+ * Estimate cost for a transfer
345
+ */
346
+ private estimateCostForTransfer(params: TransferParams): bigint {
347
+ // Base cost for deposit + withdrawal transactions
348
+ let cost = BASE_COST_LAMPORTS
349
+
350
+ // SPL token transfers have additional account rent
351
+ if (params.mint !== null) {
352
+ cost += BigInt(2_000_000) // ~0.002 SOL for token accounts
353
+ }
354
+
355
+ // Relayer fee (typically 0.1% of amount, min 0.001 SOL)
356
+ const relayerFee = params.amount / BigInt(1000)
357
+ const minRelayerFee = BigInt(1_000_000)
358
+ cost += relayerFee > minRelayerFee ? relayerFee : minRelayerFee
359
+
360
+ return cost
361
+ }
362
+
363
+ /**
364
+ * Get pool information (mocked for now)
365
+ */
366
+ private async getPoolInfo(
367
+ amount: bigint,
368
+ token?: PrivacyCashSPLToken
369
+ ): Promise<PoolInfo> {
370
+ const cacheKey = `${amount.toString()}-${token ?? 'SOL'}`
371
+
372
+ // Check cache
373
+ const cached = this.poolCache.get(cacheKey)
374
+ const expiry = this.poolCacheExpiry.get(cacheKey)
375
+ if (cached && expiry && Date.now() < expiry) {
376
+ return cached
377
+ }
378
+
379
+ // In production, query the actual pool:
380
+ // const poolInfo = await this.config.sdk?.getPoolInfo(amount, token)
381
+
382
+ // Simulated pool info based on pool size
383
+ // Larger pools typically have more depositors
384
+ const baseDepositors = token ? 30 : 50
385
+ const sizeMultiplier = Number(amount / BigInt(100_000_000))
386
+ const depositors = Math.max(
387
+ 10,
388
+ baseDepositors + Math.floor(Math.random() * 20) - Math.floor(sizeMultiplier / 10)
389
+ )
390
+
391
+ const poolInfo: PoolInfo = {
392
+ size: amount,
393
+ depositors,
394
+ liquidity: amount * BigInt(depositors),
395
+ withdrawable: depositors >= this.config.minAnonymitySet,
396
+ }
397
+
398
+ // Cache for 60 seconds
399
+ this.poolCache.set(cacheKey, poolInfo)
400
+ this.poolCacheExpiry.set(cacheKey, Date.now() + 60000)
401
+
402
+ return poolInfo
403
+ }
404
+
405
+ /**
406
+ * Get anonymity set for a specific pool
407
+ */
408
+ private async getPoolAnonymitySet(
409
+ amount: bigint,
410
+ isSOL: boolean,
411
+ mint?: string | null
412
+ ): Promise<number> {
413
+ const token = isSOL ? undefined : (mint ? this.getSPLToken(mint) : undefined)
414
+ const poolInfo = await this.getPoolInfo(amount, token)
415
+ return poolInfo.depositors
416
+ }
417
+
418
+ /**
419
+ * Get average anonymity set across cached pools
420
+ */
421
+ private getAverageAnonymitySet(): number {
422
+ if (this.poolCache.size === 0) {
423
+ return DEFAULT_ANONYMITY_SET
424
+ }
425
+
426
+ let total = 0
427
+ for (const pool of this.poolCache.values()) {
428
+ total += pool.depositors
429
+ }
430
+ return Math.round(total / this.poolCache.size)
431
+ }
432
+
433
+ /**
434
+ * Generate a simulated withdrawal note
435
+ * In production, this comes from the deposit transaction
436
+ */
437
+ private generateSimulatedNote(): string {
438
+ const randomBytes = new Uint8Array(32)
439
+ if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
440
+ crypto.getRandomValues(randomBytes)
441
+ } else {
442
+ for (let i = 0; i < 32; i++) {
443
+ randomBytes[i] = Math.floor(Math.random() * 256)
444
+ }
445
+ }
446
+ // Convert to hex string (browser-compatible, no Buffer dependency)
447
+ const hex = Array.from(randomBytes)
448
+ .map(b => b.toString(16).padStart(2, '0'))
449
+ .join('')
450
+ return `privacycash:${hex}`
451
+ }
452
+
453
+ /**
454
+ * Clear pool cache
455
+ */
456
+ clearPoolCache(): void {
457
+ this.poolCache.clear()
458
+ this.poolCacheExpiry.clear()
459
+ }
460
+ }