@sip-protocol/sdk 0.1.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.
package/src/sip.ts ADDED
@@ -0,0 +1,299 @@
1
+ /**
2
+ * SIP SDK Main Client
3
+ *
4
+ * High-level interface for interacting with the Shielded Intents Protocol.
5
+ */
6
+
7
+ import {
8
+ PrivacyLevel,
9
+ IntentStatus,
10
+ type ShieldedIntent,
11
+ type CreateIntentParams,
12
+ type TrackedIntent,
13
+ type Quote,
14
+ type FulfillmentResult,
15
+ type StealthMetaAddress,
16
+ type ViewingKey,
17
+ } from '@sip-protocol/types'
18
+ import { IntentBuilder, createShieldedIntent, trackIntent, hasRequiredProofs } from './intent'
19
+ import {
20
+ generateStealthMetaAddress,
21
+ encodeStealthMetaAddress,
22
+ decodeStealthMetaAddress,
23
+ } from './stealth'
24
+ import { generateViewingKey, deriveViewingKey } from './privacy'
25
+ import type { ChainId, HexString } from '@sip-protocol/types'
26
+ import type { ProofProvider } from './proofs'
27
+ import { ValidationError } from './errors'
28
+ import { isValidChainId } from './validation'
29
+
30
+ /**
31
+ * SIP SDK configuration
32
+ */
33
+ export interface SIPConfig {
34
+ /** Network: mainnet or testnet */
35
+ network: 'mainnet' | 'testnet'
36
+ /** Default privacy level */
37
+ defaultPrivacy?: PrivacyLevel
38
+ /** RPC endpoints for chains */
39
+ rpcEndpoints?: Partial<Record<ChainId, string>>
40
+ /**
41
+ * Proof provider for ZK proof generation
42
+ *
43
+ * If not provided, proof generation will not be available.
44
+ * Use MockProofProvider for testing, NoirProofProvider for production.
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * import { MockProofProvider } from '@sip-protocol/sdk'
49
+ *
50
+ * const sip = new SIP({
51
+ * network: 'testnet',
52
+ * proofProvider: new MockProofProvider(),
53
+ * })
54
+ * ```
55
+ */
56
+ proofProvider?: ProofProvider
57
+ }
58
+
59
+ /**
60
+ * Wallet adapter interface
61
+ */
62
+ export interface WalletAdapter {
63
+ /** Connected chain */
64
+ chain: ChainId
65
+ /** Wallet address */
66
+ address: string
67
+ /** Sign a message */
68
+ signMessage(message: string): Promise<string>
69
+ /** Sign a transaction */
70
+ signTransaction(tx: unknown): Promise<unknown>
71
+ }
72
+
73
+ /**
74
+ * Main SIP SDK class
75
+ */
76
+ export class SIP {
77
+ private config: SIPConfig
78
+ private wallet?: WalletAdapter
79
+ private stealthKeys?: {
80
+ metaAddress: StealthMetaAddress
81
+ spendingPrivateKey: HexString
82
+ viewingPrivateKey: HexString
83
+ }
84
+ private proofProvider?: ProofProvider
85
+
86
+ constructor(config: SIPConfig) {
87
+ // Validate config
88
+ if (!config || typeof config !== 'object') {
89
+ throw new ValidationError('config must be an object')
90
+ }
91
+
92
+ if (config.network !== 'mainnet' && config.network !== 'testnet') {
93
+ throw new ValidationError(
94
+ `network must be 'mainnet' or 'testnet'`,
95
+ 'config.network',
96
+ { received: config.network }
97
+ )
98
+ }
99
+
100
+ if (config.defaultPrivacy !== undefined) {
101
+ const validLevels = ['transparent', 'shielded', 'compliant']
102
+ if (!validLevels.includes(config.defaultPrivacy)) {
103
+ throw new ValidationError(
104
+ `defaultPrivacy must be one of: ${validLevels.join(', ')}`,
105
+ 'config.defaultPrivacy',
106
+ { received: config.defaultPrivacy }
107
+ )
108
+ }
109
+ }
110
+
111
+ this.config = {
112
+ ...config,
113
+ defaultPrivacy: config.defaultPrivacy ?? PrivacyLevel.SHIELDED,
114
+ }
115
+ this.proofProvider = config.proofProvider
116
+ }
117
+
118
+ /**
119
+ * Get the configured proof provider
120
+ */
121
+ getProofProvider(): ProofProvider | undefined {
122
+ return this.proofProvider
123
+ }
124
+
125
+ /**
126
+ * Set or update the proof provider
127
+ */
128
+ setProofProvider(provider: ProofProvider): void {
129
+ this.proofProvider = provider
130
+ }
131
+
132
+ /**
133
+ * Check if proof provider is available and ready
134
+ */
135
+ hasProofProvider(): boolean {
136
+ return !!(this.proofProvider && this.proofProvider.isReady)
137
+ }
138
+
139
+ /**
140
+ * Connect a wallet
141
+ */
142
+ connect(wallet: WalletAdapter): void {
143
+ this.wallet = wallet
144
+ }
145
+
146
+ /**
147
+ * Disconnect wallet
148
+ */
149
+ disconnect(): void {
150
+ this.wallet = undefined
151
+ }
152
+
153
+ /**
154
+ * Check if wallet is connected
155
+ */
156
+ isConnected(): boolean {
157
+ return !!this.wallet
158
+ }
159
+
160
+ /**
161
+ * Get connected wallet
162
+ */
163
+ getWallet(): WalletAdapter | undefined {
164
+ return this.wallet
165
+ }
166
+
167
+ /**
168
+ * Generate and store stealth keys for this session
169
+ *
170
+ * @throws {ValidationError} If chain is invalid
171
+ */
172
+ generateStealthKeys(chain: ChainId, label?: string): StealthMetaAddress {
173
+ // Validation delegated to generateStealthMetaAddress
174
+ const keys = generateStealthMetaAddress(chain, label)
175
+ this.stealthKeys = keys
176
+ return keys.metaAddress
177
+ }
178
+
179
+ /**
180
+ * Get the encoded stealth meta-address for receiving
181
+ */
182
+ getStealthAddress(): string | undefined {
183
+ if (!this.stealthKeys) return undefined
184
+ return encodeStealthMetaAddress(this.stealthKeys.metaAddress)
185
+ }
186
+
187
+ /**
188
+ * Create a new intent builder
189
+ *
190
+ * The builder is automatically configured with the SIP client's proof provider
191
+ * (if one is set), so proofs will be generated automatically when `.build()` is called.
192
+ *
193
+ * @example
194
+ * ```typescript
195
+ * const intent = await sip.intent()
196
+ * .input('near', 'NEAR', 100n)
197
+ * .output('zcash', 'ZEC', 95n)
198
+ * .privacy(PrivacyLevel.SHIELDED)
199
+ * .build()
200
+ * ```
201
+ */
202
+ intent(): IntentBuilder {
203
+ const builder = new IntentBuilder()
204
+ if (this.proofProvider) {
205
+ builder.withProvider(this.proofProvider)
206
+ }
207
+ return builder
208
+ }
209
+
210
+ /**
211
+ * Create a shielded intent directly
212
+ *
213
+ * Uses the SIP client's configured proof provider (if any) to generate proofs
214
+ * automatically for SHIELDED and COMPLIANT privacy levels.
215
+ */
216
+ async createIntent(params: CreateIntentParams): Promise<TrackedIntent> {
217
+ const intent = await createShieldedIntent(params, {
218
+ senderAddress: this.wallet?.address,
219
+ proofProvider: this.proofProvider,
220
+ })
221
+ return trackIntent(intent)
222
+ }
223
+
224
+ /**
225
+ * Get quotes for an intent (mock implementation)
226
+ */
227
+ async getQuotes(intent: ShieldedIntent): Promise<Quote[]> {
228
+ // Mock quotes for demo
229
+ const baseAmount = intent.minOutputAmount
230
+
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
+ ]
251
+ }
252
+
253
+ /**
254
+ * Execute an intent with a selected quote (mock implementation)
255
+ */
256
+ async execute(
257
+ intent: TrackedIntent,
258
+ quote: Quote,
259
+ ): 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),
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Generate a viewing key for compliant mode
274
+ */
275
+ generateViewingKey(path?: string): ViewingKey {
276
+ return generateViewingKey(path)
277
+ }
278
+
279
+ /**
280
+ * Derive a child viewing key
281
+ */
282
+ deriveViewingKey(masterKey: ViewingKey, childPath: string): ViewingKey {
283
+ return deriveViewingKey(masterKey, childPath)
284
+ }
285
+
286
+ /**
287
+ * Get network configuration
288
+ */
289
+ getNetwork(): 'mainnet' | 'testnet' {
290
+ return this.config.network
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Create a new SIP instance with default testnet config
296
+ */
297
+ export function createSIP(network: 'mainnet' | 'testnet' = 'testnet'): SIP {
298
+ return new SIP({ network })
299
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Solver Module
3
+ *
4
+ * Provides solver implementations and utilities for SIP Protocol.
5
+ */
6
+
7
+ export { MockSolver, createMockSolver } from './mock-solver'
8
+ export type { MockSolverConfig } from './mock-solver'
9
+
10
+ // Re-export solver types from types package
11
+ export type {
12
+ Solver,
13
+ SolverCapabilities,
14
+ SolverVisibleIntent,
15
+ SolverQuote,
16
+ SIPSolver,
17
+ FulfillmentStatus,
18
+ FulfillmentRequest,
19
+ FulfillmentCommitment,
20
+ FulfillmentProof,
21
+ SwapRoute,
22
+ SwapRouteStep,
23
+ SolverEvent,
24
+ SolverEventListener,
25
+ } from '@sip-protocol/types'
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Mock Solver Implementation
3
+ *
4
+ * Reference implementation of the SIPSolver interface for testing
5
+ * and development. Demonstrates how solvers should interact with
6
+ * shielded intents while preserving privacy.
7
+ */
8
+
9
+ import {
10
+ type SIPSolver,
11
+ type Solver,
12
+ type SolverCapabilities,
13
+ type SolverVisibleIntent,
14
+ type SolverQuote,
15
+ type ShieldedIntent,
16
+ type FulfillmentResult,
17
+ type FulfillmentStatus,
18
+ type ChainId,
19
+ IntentStatus,
20
+ } from '@sip-protocol/types'
21
+ import { bytesToHex, randomBytes } from '@noble/hashes/utils'
22
+
23
+ /**
24
+ * Configuration for MockSolver
25
+ */
26
+ export interface MockSolverConfig {
27
+ /** Solver name */
28
+ name?: string
29
+ /** Supported chains */
30
+ supportedChains?: ChainId[]
31
+ /** Base fee percentage (0-1) */
32
+ feePercent?: number
33
+ /** Simulated execution time in ms */
34
+ executionDelay?: number
35
+ /** Failure rate for testing (0-1) */
36
+ failureRate?: number
37
+ /** Quote spread percentage (0-1) */
38
+ spreadPercent?: number
39
+ }
40
+
41
+ /**
42
+ * Mock implementation of SIPSolver for testing
43
+ *
44
+ * This solver demonstrates the privacy-preserving interaction pattern:
45
+ * - Only accesses visible fields of intents
46
+ * - Cannot see sender identity or exact input amounts
47
+ * - Generates valid quotes based on output requirements
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * const solver = new MockSolver({ name: 'Test Solver' })
52
+ *
53
+ * // Check if solver can handle intent
54
+ * if (await solver.canHandle(visibleIntent)) {
55
+ * const quote = await solver.generateQuote(visibleIntent)
56
+ * if (quote) {
57
+ * const result = await solver.fulfill(intent, quote)
58
+ * }
59
+ * }
60
+ * ```
61
+ */
62
+ export class MockSolver implements SIPSolver {
63
+ readonly info: Solver
64
+ readonly capabilities: SolverCapabilities
65
+
66
+ private readonly feePercent: number
67
+ private readonly executionDelay: number
68
+ private readonly failureRate: number
69
+ private readonly spreadPercent: number
70
+ private readonly pendingFulfillments: Map<string, FulfillmentStatus> = new Map()
71
+
72
+ constructor(config: MockSolverConfig = {}) {
73
+ const supportedChains = config.supportedChains ?? [
74
+ 'near', 'ethereum', 'solana', 'zcash', 'polygon', 'arbitrum', 'base'
75
+ ] as ChainId[]
76
+
77
+ this.info = {
78
+ id: `mock-solver-${Date.now()}`,
79
+ name: config.name ?? 'Mock SIP Solver',
80
+ supportedChains,
81
+ reputation: 95,
82
+ totalVolume: 1000000n,
83
+ successRate: 0.99,
84
+ minOrderSize: 1n,
85
+ maxOrderSize: 1000000000n,
86
+ }
87
+
88
+ // Build supported pairs (all combinations)
89
+ const supportedPairs = new Map<string, string[]>()
90
+ for (const inputChain of supportedChains) {
91
+ const outputs = supportedChains.filter(c => c !== inputChain)
92
+ supportedPairs.set(inputChain, outputs)
93
+ }
94
+
95
+ this.capabilities = {
96
+ inputChains: supportedChains,
97
+ outputChains: supportedChains,
98
+ supportedPairs,
99
+ supportsShielded: true,
100
+ supportsCompliant: true,
101
+ supportsPartialFill: false,
102
+ avgFulfillmentTime: 30,
103
+ }
104
+
105
+ this.feePercent = config.feePercent ?? 0.005 // 0.5% default fee
106
+ this.executionDelay = config.executionDelay ?? 1000
107
+ this.failureRate = config.failureRate ?? 0
108
+ this.spreadPercent = config.spreadPercent ?? 0.01 // 1% spread
109
+ }
110
+
111
+ /**
112
+ * Check if this solver can handle the given intent
113
+ *
114
+ * Privacy-preserving: Only accesses visible fields
115
+ */
116
+ async canHandle(intent: SolverVisibleIntent): Promise<boolean> {
117
+ // Check if output chain is supported
118
+ const outputChain = intent.outputAsset.chain
119
+ if (!this.capabilities.outputChains.includes(outputChain)) {
120
+ return false
121
+ }
122
+
123
+ // Check expiry
124
+ if (intent.expiry < Date.now() / 1000) {
125
+ return false
126
+ }
127
+
128
+ // Check minimum amount (solver may have minimums)
129
+ if (intent.minOutputAmount < (this.info.minOrderSize ?? 0n)) {
130
+ return false
131
+ }
132
+
133
+ return true
134
+ }
135
+
136
+ /**
137
+ * Generate a quote for the intent
138
+ *
139
+ * Privacy-preserving:
140
+ * - Does NOT access sender identity (only senderCommitment visible)
141
+ * - Does NOT know exact input amount (only inputCommitment visible)
142
+ * - Quotes based solely on output requirements
143
+ */
144
+ async generateQuote(intent: SolverVisibleIntent): Promise<SolverQuote | null> {
145
+ // First check if we can handle this
146
+ if (!await this.canHandle(intent)) {
147
+ return null
148
+ }
149
+
150
+ // Calculate output amount with spread
151
+ // Real solvers would query liquidity pools, order books, etc.
152
+ const baseOutput = intent.minOutputAmount
153
+ const spreadAmount = (baseOutput * BigInt(Math.floor(this.spreadPercent * 10000))) / 10000n
154
+ const outputAmount = baseOutput + spreadAmount
155
+
156
+ // Calculate fee
157
+ const feeAmount = (outputAmount * BigInt(Math.floor(this.feePercent * 10000))) / 10000n
158
+
159
+ // Generate quote
160
+ const quoteId = `quote-${bytesToHex(randomBytes(8))}`
161
+ const now = Math.floor(Date.now() / 1000)
162
+
163
+ const quote: SolverQuote = {
164
+ quoteId,
165
+ intentId: intent.intentId,
166
+ solverId: this.info.id,
167
+ outputAmount,
168
+ estimatedTime: this.capabilities.avgFulfillmentTime,
169
+ expiry: now + 60, // Quote valid for 1 minute
170
+ fee: feeAmount,
171
+ signature: `0x${bytesToHex(randomBytes(64))}`, // Mock signature
172
+ validUntil: now + 60,
173
+ estimatedGas: 200000n,
174
+ }
175
+
176
+ return quote
177
+ }
178
+
179
+ /**
180
+ * Fulfill an intent with the given quote
181
+ *
182
+ * In production, this would:
183
+ * 1. Lock collateral
184
+ * 2. Execute the swap on destination chain
185
+ * 3. Generate fulfillment proof
186
+ * 4. Release collateral after verification
187
+ *
188
+ * Privacy preserved:
189
+ * - Funds go to stealth address (unlinkable)
190
+ * - Solver never learns recipient's real identity
191
+ */
192
+ async fulfill(
193
+ intent: ShieldedIntent,
194
+ quote: SolverQuote,
195
+ ): Promise<FulfillmentResult> {
196
+ // Track fulfillment status
197
+ const status: FulfillmentStatus = {
198
+ intentId: intent.intentId,
199
+ status: 'executing',
200
+ }
201
+ this.pendingFulfillments.set(intent.intentId, status)
202
+
203
+ // Simulate execution delay
204
+ await this.delay(this.executionDelay)
205
+
206
+ // Simulate random failures for testing
207
+ if (Math.random() < this.failureRate) {
208
+ status.status = 'failed'
209
+ status.error = 'Simulated failure for testing'
210
+
211
+ return {
212
+ intentId: intent.intentId,
213
+ status: IntentStatus.FAILED,
214
+ fulfilledAt: Math.floor(Date.now() / 1000),
215
+ error: status.error,
216
+ }
217
+ }
218
+
219
+ // Generate mock transaction hash
220
+ const txHash = `0x${bytesToHex(randomBytes(32))}`
221
+ status.status = 'completed'
222
+ status.txHash = txHash
223
+
224
+ return {
225
+ intentId: intent.intentId,
226
+ status: IntentStatus.FULFILLED,
227
+ outputAmount: quote.outputAmount,
228
+ txHash: intent.privacyLevel === 'transparent' ? txHash : undefined,
229
+ fulfillmentProof: {
230
+ type: 'fulfillment',
231
+ proof: `0x${bytesToHex(randomBytes(128))}` as const,
232
+ publicInputs: [
233
+ `0x${bytesToHex(new TextEncoder().encode(intent.intentId))}` as const,
234
+ `0x${bytesToHex(new TextEncoder().encode(quote.quoteId))}` as const,
235
+ ],
236
+ },
237
+ fulfilledAt: Math.floor(Date.now() / 1000),
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Cancel a pending fulfillment
243
+ */
244
+ async cancel(intentId: string): Promise<boolean> {
245
+ const status = this.pendingFulfillments.get(intentId)
246
+ if (!status || status.status !== 'pending') {
247
+ return false
248
+ }
249
+
250
+ status.status = 'cancelled'
251
+ return true
252
+ }
253
+
254
+ /**
255
+ * Get fulfillment status
256
+ */
257
+ async getStatus(intentId: string): Promise<FulfillmentStatus | null> {
258
+ return this.pendingFulfillments.get(intentId) ?? null
259
+ }
260
+
261
+ /**
262
+ * Reset solver state (for testing)
263
+ */
264
+ reset(): void {
265
+ this.pendingFulfillments.clear()
266
+ }
267
+
268
+ private delay(ms: number): Promise<void> {
269
+ return new Promise(resolve => setTimeout(resolve, ms))
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Create a mock solver with default configuration
275
+ */
276
+ export function createMockSolver(config?: MockSolverConfig): MockSolver {
277
+ return new MockSolver(config)
278
+ }