@sip-protocol/sdk 0.1.0 → 0.1.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.
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Secure Memory Utilities
3
+ *
4
+ * Provides best-effort secure memory handling for cryptographic secrets
5
+ * in JavaScript environments.
6
+ *
7
+ * ## What This Provides
8
+ * - **Explicit Cleanup**: Deterministic overwriting of sensitive buffers
9
+ * - **Defense in Depth**: Overwrite with random data, then zero
10
+ * - **API for Safe Patterns**: Helper functions for scoped secret usage
11
+ *
12
+ * ## IMPORTANT: Limitations of JavaScript Memory Cleanup
13
+ *
14
+ * JavaScript does NOT provide true secure memory guarantees. These utilities
15
+ * offer BEST-EFFORT cleanup only. Be aware of these fundamental limitations:
16
+ *
17
+ * 1. **Garbage Collection**: The GC may have already copied the original
18
+ * data to other memory locations before you call secureWipe().
19
+ *
20
+ * 2. **JIT Compilation**: Modern JS engines may optimize away "dead" writes
21
+ * or create copies in compiled code paths.
22
+ *
23
+ * 3. **Memory Swapping**: The OS may swap memory pages to disk before cleanup,
24
+ * leaving secrets in swap files or hibernation images.
25
+ *
26
+ * 4. **String Interning**: If secrets pass through strings, they may be
27
+ * interned and retained in the string pool.
28
+ *
29
+ * ## Recommendations for High-Security Applications
30
+ *
31
+ * For applications requiring strong memory protection:
32
+ * - Use hardware security modules (HSMs) for key storage
33
+ * - Use hardware wallets (Ledger, Trezor) for signing operations
34
+ * - Consider native bindings with secure memory allocators
35
+ * - Run in isolated environments with encrypted swap disabled
36
+ *
37
+ * This module is appropriate for:
38
+ * - Reducing attack surface (makes casual inspection harder)
39
+ * - Defense in depth (multiple security layers)
40
+ * - Compliance with best practices (demonstrable cleanup efforts)
41
+ *
42
+ * @see docs/security/KNOWN_LIMITATIONS.md
43
+ */
44
+
45
+ import { randomBytes } from '@noble/hashes/utils'
46
+
47
+ /**
48
+ * Securely wipe a buffer containing sensitive data
49
+ *
50
+ * This performs a defense-in-depth wipe:
51
+ * 1. Overwrite with random data (defeats simple memory scrapers)
52
+ * 2. Zero the buffer (standard cleanup)
53
+ *
54
+ * Note: Due to JavaScript's garbage collection and potential JIT
55
+ * optimizations, this cannot guarantee complete erasure. However,
56
+ * it provides significant improvement over leaving secrets in memory.
57
+ *
58
+ * @param buffer - The buffer to wipe (modified in place)
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * const secretKey = randomBytes(32)
63
+ * // ... use the key ...
64
+ * secureWipe(secretKey) // Clean up when done
65
+ * ```
66
+ */
67
+ export function secureWipe(buffer: Uint8Array): void {
68
+ if (!buffer || buffer.length === 0) {
69
+ return
70
+ }
71
+
72
+ // Step 1: Overwrite with random data
73
+ // This defeats simple memory scrapers looking for zeroed patterns
74
+ const random = randomBytes(buffer.length)
75
+ buffer.set(random)
76
+
77
+ // Step 2: Zero the buffer
78
+ // Standard cleanup - makes the data unreadable
79
+ buffer.fill(0)
80
+ }
81
+
82
+ /**
83
+ * Execute a function with a secret buffer and ensure cleanup
84
+ *
85
+ * Provides a safer pattern for using secrets - the buffer is
86
+ * automatically wiped after the function completes (or throws).
87
+ *
88
+ * @param createSecret - Function to create the secret buffer
89
+ * @param useSecret - Function that uses the secret
90
+ * @returns The result of useSecret
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * const signature = await withSecureBuffer(
95
+ * () => generatePrivateKey(),
96
+ * async (privateKey) => {
97
+ * return signMessage(message, privateKey)
98
+ * }
99
+ * )
100
+ * // privateKey is automatically wiped after signing
101
+ * ```
102
+ */
103
+ export async function withSecureBuffer<T>(
104
+ createSecret: () => Uint8Array,
105
+ useSecret: (secret: Uint8Array) => T | Promise<T>,
106
+ ): Promise<T> {
107
+ const secret = createSecret()
108
+ try {
109
+ return await useSecret(secret)
110
+ } finally {
111
+ secureWipe(secret)
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Synchronous version of withSecureBuffer
117
+ *
118
+ * @param createSecret - Function to create the secret buffer
119
+ * @param useSecret - Function that uses the secret (sync)
120
+ * @returns The result of useSecret
121
+ */
122
+ export function withSecureBufferSync<T>(
123
+ createSecret: () => Uint8Array,
124
+ useSecret: (secret: Uint8Array) => T,
125
+ ): T {
126
+ const secret = createSecret()
127
+ try {
128
+ return useSecret(secret)
129
+ } finally {
130
+ secureWipe(secret)
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Wipe multiple buffers at once
136
+ *
137
+ * Convenience function for cleaning up multiple secrets.
138
+ *
139
+ * @param buffers - Array of buffers to wipe
140
+ */
141
+ export function secureWipeAll(...buffers: (Uint8Array | undefined | null)[]): void {
142
+ for (const buffer of buffers) {
143
+ if (buffer) {
144
+ secureWipe(buffer)
145
+ }
146
+ }
147
+ }
package/src/sip.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  import {
8
8
  PrivacyLevel,
9
9
  IntentStatus,
10
+ OneClickSwapStatus,
10
11
  type ShieldedIntent,
11
12
  type CreateIntentParams,
12
13
  type TrackedIntent,
@@ -14,6 +15,8 @@ import {
14
15
  type FulfillmentResult,
15
16
  type StealthMetaAddress,
16
17
  type ViewingKey,
18
+ type OneClickQuoteResponse,
19
+ type Asset,
17
20
  } from '@sip-protocol/types'
18
21
  import { IntentBuilder, createShieldedIntent, trackIntent, hasRequiredProofs } from './intent'
19
22
  import {
@@ -24,8 +27,9 @@ import {
24
27
  import { generateViewingKey, deriveViewingKey } from './privacy'
25
28
  import type { ChainId, HexString } from '@sip-protocol/types'
26
29
  import type { ProofProvider } from './proofs'
27
- import { ValidationError } from './errors'
30
+ import { ValidationError, IntentError, ErrorCode } from './errors'
28
31
  import { isValidChainId } from './validation'
32
+ import { NEARIntentsAdapter, type NEARIntentsAdapterConfig, type SwapRequest } from './adapters'
29
33
 
30
34
  /**
31
35
  * SIP SDK configuration
@@ -33,6 +37,11 @@ import { isValidChainId } from './validation'
33
37
  export interface SIPConfig {
34
38
  /** Network: mainnet or testnet */
35
39
  network: 'mainnet' | 'testnet'
40
+ /**
41
+ * Mode: 'demo' for mock data, 'production' for real NEAR Intents
42
+ * @default 'demo'
43
+ */
44
+ mode?: 'demo' | 'production'
36
45
  /** Default privacy level */
37
46
  defaultPrivacy?: PrivacyLevel
38
47
  /** RPC endpoints for chains */
@@ -54,6 +63,23 @@ export interface SIPConfig {
54
63
  * ```
55
64
  */
56
65
  proofProvider?: ProofProvider
66
+ /**
67
+ * NEAR Intents adapter configuration
68
+ *
69
+ * Required for production mode. Provides connection to 1Click API.
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const sip = new SIP({
74
+ * network: 'mainnet',
75
+ * mode: 'production',
76
+ * intentsAdapter: {
77
+ * jwtToken: process.env.NEAR_INTENTS_JWT,
78
+ * },
79
+ * })
80
+ * ```
81
+ */
82
+ intentsAdapter?: NEARIntentsAdapterConfig | NEARIntentsAdapter
57
83
  }
58
84
 
59
85
  /**
@@ -68,13 +94,25 @@ export interface WalletAdapter {
68
94
  signMessage(message: string): Promise<string>
69
95
  /** Sign a transaction */
70
96
  signTransaction(tx: unknown): Promise<unknown>
97
+ /** Send a transaction (optional) */
98
+ sendTransaction?(tx: unknown): Promise<string>
99
+ }
100
+
101
+ /**
102
+ * Extended quote with deposit info for production mode
103
+ */
104
+ export interface ProductionQuote extends Quote {
105
+ /** Deposit address for input tokens (production mode only) */
106
+ depositAddress?: string
107
+ /** Raw 1Click quote response (production mode only) */
108
+ rawQuote?: OneClickQuoteResponse
71
109
  }
72
110
 
73
111
  /**
74
112
  * Main SIP SDK class
75
113
  */
76
114
  export class SIP {
77
- private config: SIPConfig
115
+ private config: SIPConfig & { mode: 'demo' | 'production' }
78
116
  private wallet?: WalletAdapter
79
117
  private stealthKeys?: {
80
118
  metaAddress: StealthMetaAddress
@@ -82,6 +120,9 @@ export class SIP {
82
120
  viewingPrivateKey: HexString
83
121
  }
84
122
  private proofProvider?: ProofProvider
123
+ private intentsAdapter?: NEARIntentsAdapter
124
+ /** Cache of pending swaps by intent ID */
125
+ private pendingSwaps: Map<string, { depositAddress: string; quote: OneClickQuoteResponse }> = new Map()
85
126
 
86
127
  constructor(config: SIPConfig) {
87
128
  // Validate config
@@ -97,6 +138,14 @@ export class SIP {
97
138
  )
98
139
  }
99
140
 
141
+ if (config.mode !== undefined && config.mode !== 'demo' && config.mode !== 'production') {
142
+ throw new ValidationError(
143
+ `mode must be 'demo' or 'production'`,
144
+ 'config.mode',
145
+ { received: config.mode }
146
+ )
147
+ }
148
+
100
149
  if (config.defaultPrivacy !== undefined) {
101
150
  const validLevels = ['transparent', 'shielded', 'compliant']
102
151
  if (!validLevels.includes(config.defaultPrivacy)) {
@@ -110,9 +159,51 @@ export class SIP {
110
159
 
111
160
  this.config = {
112
161
  ...config,
162
+ mode: config.mode ?? 'demo',
113
163
  defaultPrivacy: config.defaultPrivacy ?? PrivacyLevel.SHIELDED,
114
164
  }
115
165
  this.proofProvider = config.proofProvider
166
+
167
+ // Initialize intents adapter if provided
168
+ if (config.intentsAdapter) {
169
+ if (config.intentsAdapter instanceof NEARIntentsAdapter) {
170
+ this.intentsAdapter = config.intentsAdapter
171
+ } else {
172
+ this.intentsAdapter = new NEARIntentsAdapter(config.intentsAdapter)
173
+ }
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Get the current mode
179
+ */
180
+ getMode(): 'demo' | 'production' {
181
+ return this.config.mode
182
+ }
183
+
184
+ /**
185
+ * Check if running in production mode with real NEAR Intents
186
+ */
187
+ isProductionMode(): boolean {
188
+ return this.config.mode === 'production' && !!this.intentsAdapter
189
+ }
190
+
191
+ /**
192
+ * Get the NEAR Intents adapter
193
+ */
194
+ getIntentsAdapter(): NEARIntentsAdapter | undefined {
195
+ return this.intentsAdapter
196
+ }
197
+
198
+ /**
199
+ * Set the NEAR Intents adapter
200
+ */
201
+ setIntentsAdapter(adapter: NEARIntentsAdapter | NEARIntentsAdapterConfig): void {
202
+ if (adapter instanceof NEARIntentsAdapter) {
203
+ this.intentsAdapter = adapter
204
+ } else {
205
+ this.intentsAdapter = new NEARIntentsAdapter(adapter)
206
+ }
116
207
  }
117
208
 
118
209
  /**
@@ -222,51 +313,65 @@ export class SIP {
222
313
  }
223
314
 
224
315
  /**
225
- * Get quotes for an intent (mock implementation)
316
+ * Get quotes for an intent
317
+ *
318
+ * In production mode: fetches real quotes from NEAR 1Click API
319
+ * In demo mode: returns mock quotes for testing
320
+ *
321
+ * @param params - Intent parameters (CreateIntentParams for production, ShieldedIntent/CreateIntentParams for demo)
322
+ * @param recipientMetaAddress - Optional stealth meta-address for privacy modes
323
+ * @returns Array of quotes (with deposit info in production mode)
226
324
  */
227
- async getQuotes(intent: ShieldedIntent): Promise<Quote[]> {
228
- // Mock quotes for demo
229
- const baseAmount = intent.minOutputAmount
325
+ async getQuotes(
326
+ params: CreateIntentParams | ShieldedIntent,
327
+ recipientMetaAddress?: StealthMetaAddress | string,
328
+ ): Promise<ProductionQuote[]> {
329
+ // Production mode - use real NEAR Intents
330
+ if (this.isProductionMode()) {
331
+ // Production mode requires CreateIntentParams with raw values
332
+ if (!('input' in params)) {
333
+ throw new ValidationError(
334
+ 'Production mode requires CreateIntentParams with raw input/output values. ShieldedIntent does not expose raw values.',
335
+ 'params'
336
+ )
337
+ }
338
+ return this.getQuotesProduction(params, recipientMetaAddress)
339
+ }
230
340
 
231
- return [
232
- {
233
- quoteId: `quote-${Date.now()}-1`,
234
- intentId: intent.intentId,
235
- solverId: 'solver-1',
236
- outputAmount: baseAmount + (baseAmount * 2n) / 100n, // +2%
237
- estimatedTime: 30,
238
- expiry: Math.floor(Date.now() / 1000) + 60,
239
- fee: baseAmount / 200n, // 0.5%
240
- },
241
- {
242
- quoteId: `quote-${Date.now()}-2`,
243
- intentId: intent.intentId,
244
- solverId: 'solver-2',
245
- outputAmount: baseAmount + (baseAmount * 1n) / 100n, // +1%
246
- estimatedTime: 15,
247
- expiry: Math.floor(Date.now() / 1000) + 60,
248
- fee: baseAmount / 100n, // 1%
249
- },
250
- ]
341
+ // Demo mode - return mock quotes
342
+ return this.getQuotesDemo(params)
251
343
  }
252
344
 
253
345
  /**
254
- * Execute an intent with a selected quote (mock implementation)
346
+ * Execute an intent with a selected quote
347
+ *
348
+ * In production mode: initiates real swap via NEAR 1Click API
349
+ * In demo mode: returns mock result
350
+ *
351
+ * @param intent - The intent to execute
352
+ * @param quote - Selected quote from getQuotes()
353
+ * @param options - Execution options
354
+ * @returns Fulfillment result with transaction hash (when available)
255
355
  */
256
356
  async execute(
257
357
  intent: TrackedIntent,
258
- quote: Quote,
358
+ quote: Quote | ProductionQuote,
359
+ options?: {
360
+ /** Callback when deposit is required */
361
+ onDepositRequired?: (depositAddress: string, amount: string) => Promise<string>
362
+ /** Callback for status updates */
363
+ onStatusUpdate?: (status: OneClickSwapStatus) => void
364
+ /** Timeout for waiting (ms) */
365
+ timeout?: number
366
+ },
259
367
  ): Promise<FulfillmentResult> {
260
- // Mock execution
261
- await new Promise((resolve) => setTimeout(resolve, 2000))
262
-
263
- return {
264
- intentId: intent.intentId,
265
- status: IntentStatus.FULFILLED,
266
- outputAmount: quote.outputAmount,
267
- txHash: intent.privacyLevel === PrivacyLevel.TRANSPARENT ? `0x${Date.now().toString(16)}` : undefined,
268
- fulfilledAt: Math.floor(Date.now() / 1000),
368
+ // Production mode - use real NEAR Intents
369
+ if (this.isProductionMode()) {
370
+ return this.executeProduction(intent, quote as ProductionQuote, options)
269
371
  }
372
+
373
+ // Demo mode - return mock result
374
+ return this.executeDemo(intent, quote)
270
375
  }
271
376
 
272
377
  /**
@@ -289,6 +394,245 @@ export class SIP {
289
394
  getNetwork(): 'mainnet' | 'testnet' {
290
395
  return this.config.network
291
396
  }
397
+
398
+ // ─── Production Mode Implementation ─────────────────────────────────────────
399
+
400
+ private async getQuotesProduction(
401
+ params: CreateIntentParams,
402
+ recipientMetaAddress?: StealthMetaAddress | string,
403
+ ): Promise<ProductionQuote[]> {
404
+ if (!this.intentsAdapter) {
405
+ throw new ValidationError(
406
+ 'NEAR Intents adapter not configured. Set intentsAdapter in config for production mode.',
407
+ 'intentsAdapter'
408
+ )
409
+ }
410
+
411
+ // For privacy modes, require stealth meta-address
412
+ const metaAddr = recipientMetaAddress ?? (
413
+ params.privacy !== PrivacyLevel.TRANSPARENT
414
+ ? this.stealthKeys?.metaAddress
415
+ : undefined
416
+ )
417
+
418
+ if (params.privacy !== PrivacyLevel.TRANSPARENT && !metaAddr) {
419
+ throw new ValidationError(
420
+ 'Stealth meta-address required for privacy modes. Call generateStealthKeys() or provide recipientMetaAddress.',
421
+ 'recipientMetaAddress'
422
+ )
423
+ }
424
+
425
+ // Generate a request ID for tracking
426
+ const requestId = `quote-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
427
+
428
+ // Build swap request from CreateIntentParams
429
+ const swapRequest: SwapRequest = {
430
+ requestId,
431
+ privacyLevel: params.privacy,
432
+ inputAsset: params.input.asset,
433
+ outputAsset: params.output.asset,
434
+ inputAmount: params.input.amount,
435
+ minOutputAmount: params.output.minAmount,
436
+ }
437
+
438
+ try {
439
+ // Get quote from 1Click API
440
+ const prepared = await this.intentsAdapter.prepareSwap(
441
+ swapRequest,
442
+ metaAddr,
443
+ this.wallet?.address,
444
+ )
445
+ const rawQuote = await this.intentsAdapter.getQuote(prepared)
446
+
447
+ // Cache for execute()
448
+ this.pendingSwaps.set(requestId, {
449
+ depositAddress: rawQuote.depositAddress,
450
+ quote: rawQuote,
451
+ })
452
+
453
+ // Convert to SIP Quote format
454
+ const quote: ProductionQuote = {
455
+ quoteId: rawQuote.quoteId,
456
+ intentId: requestId,
457
+ solverId: 'near-1click',
458
+ outputAmount: BigInt(rawQuote.amountOut),
459
+ estimatedTime: rawQuote.timeEstimate,
460
+ expiry: Math.floor(new Date(rawQuote.deadline).getTime() / 1000),
461
+ fee: this.calculateFee(params.input.amount, BigInt(rawQuote.amountIn)),
462
+ depositAddress: rawQuote.depositAddress,
463
+ rawQuote,
464
+ }
465
+
466
+ return [quote]
467
+ } catch (error) {
468
+ // If production fails, don't fall back to demo - let the error propagate
469
+ throw error
470
+ }
471
+ }
472
+
473
+ private async executeProduction(
474
+ intent: TrackedIntent,
475
+ quote: ProductionQuote,
476
+ options?: {
477
+ onDepositRequired?: (depositAddress: string, amount: string) => Promise<string>
478
+ onStatusUpdate?: (status: OneClickSwapStatus) => void
479
+ timeout?: number
480
+ },
481
+ ): Promise<FulfillmentResult> {
482
+ if (!this.intentsAdapter) {
483
+ throw new ValidationError(
484
+ 'NEAR Intents adapter not configured',
485
+ 'intentsAdapter'
486
+ )
487
+ }
488
+
489
+ // Get deposit info from quote or cache
490
+ const pendingSwap = this.pendingSwaps.get(quote.intentId)
491
+ const depositAddress = quote.depositAddress ?? pendingSwap?.depositAddress
492
+ const rawQuote = quote.rawQuote ?? pendingSwap?.quote
493
+
494
+ if (!depositAddress || !rawQuote) {
495
+ throw new IntentError(
496
+ 'No deposit address found. Call getQuotes() first.',
497
+ ErrorCode.INTENT_NOT_FOUND,
498
+ { intentId: intent.intentId }
499
+ )
500
+ }
501
+
502
+ // If wallet can send transactions and callback provided, handle deposit
503
+ let depositTxHash: string | undefined
504
+ if (options?.onDepositRequired) {
505
+ depositTxHash = await options.onDepositRequired(depositAddress, rawQuote.amountIn)
506
+
507
+ // Validate txHash format before proceeding
508
+ if (!depositTxHash || !this.isValidTxHash(depositTxHash)) {
509
+ throw new ValidationError(
510
+ 'Invalid deposit transaction hash. Expected 0x-prefixed hex string (32-66 bytes).',
511
+ 'depositTxHash',
512
+ { received: depositTxHash }
513
+ )
514
+ }
515
+
516
+ // Notify 1Click of deposit
517
+ await this.intentsAdapter.notifyDeposit(
518
+ depositAddress,
519
+ depositTxHash,
520
+ )
521
+ }
522
+
523
+ // Wait for completion
524
+ const finalStatus = await this.intentsAdapter.waitForCompletion(
525
+ depositAddress,
526
+ {
527
+ timeout: options?.timeout ?? 300000, // 5 minutes default
528
+ onStatus: (status) => options?.onStatusUpdate?.(status.status),
529
+ },
530
+ )
531
+
532
+ // Clean up cache
533
+ this.pendingSwaps.delete(quote.intentId)
534
+
535
+ // Convert to FulfillmentResult
536
+ const isSuccess = finalStatus.status === OneClickSwapStatus.SUCCESS
537
+ return {
538
+ intentId: intent.intentId,
539
+ status: isSuccess ? IntentStatus.FULFILLED : IntentStatus.FAILED,
540
+ outputAmount: quote.outputAmount,
541
+ txHash: finalStatus.settlementTxHash ?? depositTxHash,
542
+ fulfilledAt: Math.floor(Date.now() / 1000),
543
+ error: finalStatus.error,
544
+ }
545
+ }
546
+
547
+ private calculateFee(inputAmount: bigint, quotedInput: bigint): bigint {
548
+ // Fee is the difference between what we sent and what was quoted
549
+ if (quotedInput > inputAmount) {
550
+ return quotedInput - inputAmount
551
+ }
552
+ // Estimate 0.5% fee if we can't calculate
553
+ return inputAmount / 200n
554
+ }
555
+
556
+ /**
557
+ * Validate transaction hash format
558
+ *
559
+ * Accepts:
560
+ * - Ethereum-style: 0x + 64 hex chars (32 bytes)
561
+ * - Solana-style: base58 encoded (44-88 chars)
562
+ * - NEAR-style: base58 or hex with varying lengths
563
+ */
564
+ private isValidTxHash(txHash: string): boolean {
565
+ if (!txHash || typeof txHash !== 'string') {
566
+ return false
567
+ }
568
+
569
+ // Check for 0x-prefixed hex (Ethereum, etc.)
570
+ if (txHash.startsWith('0x')) {
571
+ const hex = txHash.slice(2)
572
+ // Valid hex, 32-66 bytes (64-132 chars)
573
+ return /^[0-9a-fA-F]{64,132}$/.test(hex)
574
+ }
575
+
576
+ // Base58 (Solana, NEAR)
577
+ if (/^[1-9A-HJ-NP-Za-km-z]{32,88}$/.test(txHash)) {
578
+ return true
579
+ }
580
+
581
+ return false
582
+ }
583
+
584
+ // ─── Demo Mode Implementation ───────────────────────────────────────────────
585
+
586
+ private async getQuotesDemo(params: CreateIntentParams | ShieldedIntent): Promise<ProductionQuote[]> {
587
+ // Extract base amount depending on type
588
+ const baseAmount = 'input' in params
589
+ ? params.output.minAmount // CreateIntentParams
590
+ : params.minOutputAmount // ShieldedIntent
591
+
592
+ // Generate intentId if not present
593
+ const intentId = 'intentId' in params
594
+ ? params.intentId
595
+ : `demo-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
596
+
597
+ return [
598
+ {
599
+ quoteId: `quote-${Date.now()}-1`,
600
+ intentId,
601
+ solverId: 'demo-solver-1',
602
+ outputAmount: baseAmount + (baseAmount * 2n) / 100n, // +2%
603
+ estimatedTime: 30,
604
+ expiry: Math.floor(Date.now() / 1000) + 60,
605
+ fee: baseAmount / 200n, // 0.5%
606
+ },
607
+ {
608
+ quoteId: `quote-${Date.now()}-2`,
609
+ intentId,
610
+ solverId: 'demo-solver-2',
611
+ outputAmount: baseAmount + (baseAmount * 1n) / 100n, // +1%
612
+ estimatedTime: 15,
613
+ expiry: Math.floor(Date.now() / 1000) + 60,
614
+ fee: baseAmount / 100n, // 1%
615
+ },
616
+ ]
617
+ }
618
+
619
+ private async executeDemo(
620
+ intent: TrackedIntent,
621
+ quote: Quote,
622
+ ): Promise<FulfillmentResult> {
623
+ // Simulate execution delay
624
+ await new Promise((resolve) => setTimeout(resolve, 2000))
625
+
626
+ return {
627
+ intentId: intent.intentId,
628
+ status: IntentStatus.FULFILLED,
629
+ outputAmount: quote.outputAmount,
630
+ txHash: intent.privacyLevel === PrivacyLevel.TRANSPARENT
631
+ ? `0x${Date.now().toString(16)}`
632
+ : undefined,
633
+ fulfilledAt: Math.floor(Date.now() / 1000),
634
+ }
635
+ }
292
636
  }
293
637
 
294
638
  /**
@@ -297,3 +641,21 @@ export class SIP {
297
641
  export function createSIP(network: 'mainnet' | 'testnet' = 'testnet'): SIP {
298
642
  return new SIP({ network })
299
643
  }
644
+
645
+ /**
646
+ * Create a new SIP instance configured for production
647
+ */
648
+ export function createProductionSIP(config: {
649
+ network: 'mainnet' | 'testnet'
650
+ jwtToken?: string
651
+ proofProvider?: ProofProvider
652
+ }): SIP {
653
+ return new SIP({
654
+ network: config.network,
655
+ mode: 'production',
656
+ proofProvider: config.proofProvider,
657
+ intentsAdapter: {
658
+ jwtToken: config.jwtToken,
659
+ },
660
+ })
661
+ }