@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/errors.ts ADDED
@@ -0,0 +1,471 @@
1
+ /**
2
+ * Custom errors for SIP Protocol SDK
3
+ *
4
+ * Provides a comprehensive error hierarchy with:
5
+ * - Machine-readable error codes
6
+ * - Human-readable messages
7
+ * - Original cause preservation
8
+ * - Additional debugging context
9
+ * - Serialization for logging
10
+ */
11
+
12
+ // ─── Error Codes ─────────────────────────────────────────────────────────────
13
+
14
+ /**
15
+ * Machine-readable error codes for programmatic error handling
16
+ */
17
+ export enum ErrorCode {
18
+ // General errors (1xxx)
19
+ UNKNOWN = 'SIP_1000',
20
+ INTERNAL = 'SIP_1001',
21
+ NOT_IMPLEMENTED = 'SIP_1002',
22
+
23
+ // Validation errors (2xxx)
24
+ VALIDATION_FAILED = 'SIP_2000',
25
+ INVALID_INPUT = 'SIP_2001',
26
+ INVALID_CHAIN = 'SIP_2002',
27
+ INVALID_PRIVACY_LEVEL = 'SIP_2003',
28
+ INVALID_AMOUNT = 'SIP_2004',
29
+ INVALID_HEX = 'SIP_2005',
30
+ INVALID_KEY = 'SIP_2006',
31
+ INVALID_ADDRESS = 'SIP_2007',
32
+ MISSING_REQUIRED = 'SIP_2008',
33
+ OUT_OF_RANGE = 'SIP_2009',
34
+
35
+ // Cryptographic errors (3xxx)
36
+ CRYPTO_FAILED = 'SIP_3000',
37
+ ENCRYPTION_FAILED = 'SIP_3001',
38
+ DECRYPTION_FAILED = 'SIP_3002',
39
+ KEY_DERIVATION_FAILED = 'SIP_3003',
40
+ COMMITMENT_FAILED = 'SIP_3004',
41
+ SIGNATURE_FAILED = 'SIP_3005',
42
+ INVALID_CURVE_POINT = 'SIP_3006',
43
+ INVALID_SCALAR = 'SIP_3007',
44
+
45
+ // Proof errors (4xxx)
46
+ PROOF_FAILED = 'SIP_4000',
47
+ PROOF_GENERATION_FAILED = 'SIP_4001',
48
+ PROOF_VERIFICATION_FAILED = 'SIP_4002',
49
+ PROOF_NOT_IMPLEMENTED = 'SIP_4003',
50
+ PROOF_PROVIDER_NOT_READY = 'SIP_4004',
51
+ INVALID_PROOF_PARAMS = 'SIP_4005',
52
+
53
+ // Intent errors (5xxx)
54
+ INTENT_FAILED = 'SIP_5000',
55
+ INTENT_EXPIRED = 'SIP_5001',
56
+ INTENT_CANCELLED = 'SIP_5002',
57
+ INTENT_NOT_FOUND = 'SIP_5003',
58
+ INTENT_INVALID_STATE = 'SIP_5004',
59
+ PROOFS_REQUIRED = 'SIP_5005',
60
+ QUOTE_EXPIRED = 'SIP_5006',
61
+
62
+ // Network errors (6xxx)
63
+ NETWORK_FAILED = 'SIP_6000',
64
+ NETWORK_TIMEOUT = 'SIP_6001',
65
+ NETWORK_UNAVAILABLE = 'SIP_6002',
66
+ RPC_ERROR = 'SIP_6003',
67
+ API_ERROR = 'SIP_6004',
68
+ RATE_LIMITED = 'SIP_6005',
69
+
70
+ // Wallet errors (7xxx)
71
+ WALLET_ERROR = 'SIP_7000',
72
+ WALLET_NOT_CONNECTED = 'SIP_7001',
73
+ WALLET_CONNECTION_FAILED = 'SIP_7002',
74
+ WALLET_SIGNING_FAILED = 'SIP_7003',
75
+ WALLET_TRANSACTION_FAILED = 'SIP_7004',
76
+ }
77
+
78
+ // ─── Serialized Error Type ───────────────────────────────────────────────────
79
+
80
+ /**
81
+ * Serialized error format for logging and transmission
82
+ */
83
+ export interface SerializedError {
84
+ name: string
85
+ code: ErrorCode
86
+ message: string
87
+ field?: string
88
+ context?: Record<string, unknown>
89
+ cause?: string
90
+ stack?: string
91
+ timestamp: string
92
+ }
93
+
94
+ // ─── Base Error Class ────────────────────────────────────────────────────────
95
+
96
+ /**
97
+ * Base error class for SIP Protocol
98
+ *
99
+ * All SDK errors extend this class and include:
100
+ * - `code`: Machine-readable error code for programmatic handling
101
+ * - `cause`: Original error if this error wraps another
102
+ * - `context`: Additional debugging information
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * try {
107
+ * await sip.execute(intent, quote)
108
+ * } catch (e) {
109
+ * if (e instanceof SIPError) {
110
+ * console.log(`Error ${e.code}: ${e.message}`)
111
+ * if (e.cause) console.log('Caused by:', e.cause)
112
+ * }
113
+ * }
114
+ * ```
115
+ */
116
+ export class SIPError extends Error {
117
+ /** Machine-readable error code */
118
+ readonly code: ErrorCode
119
+
120
+ /** Additional debugging context */
121
+ readonly context?: Record<string, unknown>
122
+
123
+ /** Timestamp when error was created */
124
+ readonly timestamp: Date
125
+
126
+ constructor(
127
+ message: string,
128
+ code: ErrorCode = ErrorCode.UNKNOWN,
129
+ options?: {
130
+ cause?: Error
131
+ context?: Record<string, unknown>
132
+ }
133
+ ) {
134
+ super(message, { cause: options?.cause })
135
+ this.name = 'SIPError'
136
+ this.code = code
137
+ this.context = options?.context
138
+ this.timestamp = new Date()
139
+
140
+ // Preserve stack trace
141
+ if (Error.captureStackTrace) {
142
+ Error.captureStackTrace(this, this.constructor)
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Serialize error for logging or transmission
148
+ */
149
+ toJSON(): SerializedError {
150
+ return {
151
+ name: this.name,
152
+ code: this.code,
153
+ message: this.message,
154
+ context: this.context,
155
+ cause: this.cause instanceof Error ? this.cause.message : undefined,
156
+ stack: this.stack,
157
+ timestamp: this.timestamp.toISOString(),
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Create a string representation for logging
163
+ */
164
+ toString(): string {
165
+ let result = `[${this.code}] ${this.name}: ${this.message}`
166
+ if (this.cause instanceof Error) {
167
+ result += `\n Caused by: ${this.cause.message}`
168
+ }
169
+ return result
170
+ }
171
+ }
172
+
173
+ // ─── Validation Error ────────────────────────────────────────────────────────
174
+
175
+ /**
176
+ * Error thrown when input validation fails
177
+ *
178
+ * Provides detailed information about what validation failed
179
+ * and optionally which field caused the error.
180
+ *
181
+ * @example
182
+ * ```typescript
183
+ * throw new ValidationError('Amount must be positive', 'input.amount')
184
+ *
185
+ * // With error code
186
+ * throw new ValidationError('Invalid chain ID', 'chain', {
187
+ * code: ErrorCode.INVALID_CHAIN,
188
+ * context: { received: 'invalid-chain' }
189
+ * })
190
+ * ```
191
+ */
192
+ export class ValidationError extends SIPError {
193
+ /** The field that failed validation (if applicable) */
194
+ readonly field?: string
195
+
196
+ constructor(
197
+ message: string,
198
+ field?: string,
199
+ context?: Record<string, unknown>,
200
+ code: ErrorCode = ErrorCode.VALIDATION_FAILED,
201
+ ) {
202
+ const fullMessage = field
203
+ ? `Validation failed for '${field}': ${message}`
204
+ : `Validation failed: ${message}`
205
+ super(fullMessage, code, { context })
206
+ this.name = 'ValidationError'
207
+ this.field = field
208
+ }
209
+
210
+ toJSON(): SerializedError {
211
+ return {
212
+ ...super.toJSON(),
213
+ field: this.field,
214
+ }
215
+ }
216
+ }
217
+
218
+ // ─── Cryptographic Error ─────────────────────────────────────────────────────
219
+
220
+ /**
221
+ * Error thrown when cryptographic operations fail
222
+ *
223
+ * Covers encryption, decryption, key derivation, commitments, and signatures.
224
+ *
225
+ * @example
226
+ * ```typescript
227
+ * throw new CryptoError('Decryption failed', ErrorCode.DECRYPTION_FAILED, {
228
+ * cause: originalError,
229
+ * context: { operation: 'decryptWithViewing' }
230
+ * })
231
+ * ```
232
+ */
233
+ export class CryptoError extends SIPError {
234
+ /** The cryptographic operation that failed */
235
+ readonly operation?: string
236
+
237
+ constructor(
238
+ message: string,
239
+ code: ErrorCode = ErrorCode.CRYPTO_FAILED,
240
+ options?: {
241
+ cause?: Error
242
+ context?: Record<string, unknown>
243
+ operation?: string
244
+ }
245
+ ) {
246
+ super(message, code, options)
247
+ this.name = 'CryptoError'
248
+ this.operation = options?.operation
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Error thrown when encryption functions are called but not yet implemented
254
+ *
255
+ * @deprecated Use CryptoError with ErrorCode.NOT_IMPLEMENTED instead
256
+ */
257
+ export class EncryptionNotImplementedError extends CryptoError {
258
+ /** The type of encryption operation */
259
+ readonly operationType: 'encrypt' | 'decrypt'
260
+
261
+ /** Reference to the specification document */
262
+ readonly specReference: string
263
+
264
+ constructor(
265
+ operation: 'encrypt' | 'decrypt',
266
+ specReference: string,
267
+ ) {
268
+ const message = `${operation.charAt(0).toUpperCase() + operation.slice(1)}ion is not implemented. ` +
269
+ `Real authenticated encryption (ChaCha20-Poly1305) is required. ` +
270
+ `See specification: ${specReference}`
271
+ super(message, ErrorCode.NOT_IMPLEMENTED, {
272
+ context: { operation, specReference }
273
+ })
274
+ this.name = 'EncryptionNotImplementedError'
275
+ this.operationType = operation
276
+ this.specReference = specReference
277
+ }
278
+ }
279
+
280
+ // ─── Proof Error ─────────────────────────────────────────────────────────────
281
+
282
+ /**
283
+ * Error thrown when proof operations fail
284
+ *
285
+ * Covers proof generation, verification, and related operations.
286
+ *
287
+ * @example
288
+ * ```typescript
289
+ * throw new ProofError('Proof verification failed', ErrorCode.PROOF_VERIFICATION_FAILED, {
290
+ * context: { proofType: 'funding', publicInputs: [...] }
291
+ * })
292
+ * ```
293
+ */
294
+ export class ProofError extends SIPError {
295
+ /** The type of proof involved */
296
+ readonly proofType?: 'funding' | 'validity' | 'fulfillment' | 'viewing'
297
+
298
+ constructor(
299
+ message: string,
300
+ code: ErrorCode = ErrorCode.PROOF_FAILED,
301
+ options?: {
302
+ cause?: Error
303
+ context?: Record<string, unknown>
304
+ proofType?: 'funding' | 'validity' | 'fulfillment' | 'viewing'
305
+ }
306
+ ) {
307
+ super(message, code, options)
308
+ this.name = 'ProofError'
309
+ this.proofType = options?.proofType
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Error thrown when a proof function is called but not yet implemented
315
+ *
316
+ * This error indicates that real ZK proof generation is required but not available.
317
+ *
318
+ * @example
319
+ * ```typescript
320
+ * // Use ProofProvider for proof generation
321
+ * const provider = new NoirProofProvider(config)
322
+ * await provider.initialize()
323
+ * const result = await provider.generateFundingProof(params)
324
+ * ```
325
+ */
326
+ export class ProofNotImplementedError extends ProofError {
327
+ /** Reference to the specification document */
328
+ readonly specReference: string
329
+
330
+ constructor(
331
+ proofType: 'funding' | 'validity' | 'fulfillment' | 'viewing',
332
+ specReference: string,
333
+ ) {
334
+ const message = `${proofType.charAt(0).toUpperCase() + proofType.slice(1)} proof generation is not implemented. ` +
335
+ `Real ZK proofs are required for production use. ` +
336
+ `See specification: ${specReference}`
337
+ super(message, ErrorCode.PROOF_NOT_IMPLEMENTED, {
338
+ context: { specReference },
339
+ proofType,
340
+ })
341
+ this.name = 'ProofNotImplementedError'
342
+ this.specReference = specReference
343
+ }
344
+ }
345
+
346
+ // ─── Intent Error ────────────────────────────────────────────────────────────
347
+
348
+ /**
349
+ * Error thrown when intent operations fail
350
+ *
351
+ * Covers intent creation, execution, and lifecycle errors.
352
+ *
353
+ * @example
354
+ * ```typescript
355
+ * throw new IntentError('Intent has expired', ErrorCode.INTENT_EXPIRED, {
356
+ * context: { intentId, expiry, now: Date.now() }
357
+ * })
358
+ * ```
359
+ */
360
+ export class IntentError extends SIPError {
361
+ /** The intent ID involved (if available) */
362
+ readonly intentId?: string
363
+
364
+ constructor(
365
+ message: string,
366
+ code: ErrorCode = ErrorCode.INTENT_FAILED,
367
+ options?: {
368
+ cause?: Error
369
+ context?: Record<string, unknown>
370
+ intentId?: string
371
+ }
372
+ ) {
373
+ super(message, code, options)
374
+ this.name = 'IntentError'
375
+ this.intentId = options?.intentId
376
+ }
377
+ }
378
+
379
+ // ─── Network Error ───────────────────────────────────────────────────────────
380
+
381
+ /**
382
+ * Error thrown when external service communication fails
383
+ *
384
+ * Covers RPC calls, API requests, and network connectivity issues.
385
+ *
386
+ * @example
387
+ * ```typescript
388
+ * throw new NetworkError('RPC request failed', ErrorCode.RPC_ERROR, {
389
+ * cause: originalError,
390
+ * context: { endpoint: 'https://...', method: 'eth_call' }
391
+ * })
392
+ * ```
393
+ */
394
+ export class NetworkError extends SIPError {
395
+ /** The endpoint that failed (if applicable) */
396
+ readonly endpoint?: string
397
+
398
+ /** HTTP status code (if applicable) */
399
+ readonly statusCode?: number
400
+
401
+ constructor(
402
+ message: string,
403
+ code: ErrorCode = ErrorCode.NETWORK_FAILED,
404
+ options?: {
405
+ cause?: Error
406
+ context?: Record<string, unknown>
407
+ endpoint?: string
408
+ statusCode?: number
409
+ }
410
+ ) {
411
+ super(message, code, options)
412
+ this.name = 'NetworkError'
413
+ this.endpoint = options?.endpoint
414
+ this.statusCode = options?.statusCode
415
+ }
416
+ }
417
+
418
+ // ─── Error Utilities ─────────────────────────────────────────────────────────
419
+
420
+ /**
421
+ * Check if an error is a SIP Protocol error
422
+ */
423
+ export function isSIPError(error: unknown): error is SIPError {
424
+ return error instanceof SIPError
425
+ }
426
+
427
+ /**
428
+ * Check if an error has a specific error code
429
+ */
430
+ export function hasErrorCode(error: unknown, code: ErrorCode): boolean {
431
+ return isSIPError(error) && error.code === code
432
+ }
433
+
434
+ /**
435
+ * Wrap an unknown error as a SIPError
436
+ *
437
+ * Useful for catching and re-throwing with additional context.
438
+ *
439
+ * @example
440
+ * ```typescript
441
+ * try {
442
+ * await riskyOperation()
443
+ * } catch (e) {
444
+ * throw wrapError(e, 'Operation failed', ErrorCode.INTERNAL)
445
+ * }
446
+ * ```
447
+ */
448
+ export function wrapError(
449
+ error: unknown,
450
+ message: string,
451
+ code: ErrorCode = ErrorCode.INTERNAL,
452
+ context?: Record<string, unknown>
453
+ ): SIPError {
454
+ if (error instanceof SIPError) {
455
+ return error
456
+ }
457
+
458
+ const cause = error instanceof Error ? error : new Error(String(error))
459
+
460
+ return new SIPError(message, code, { cause, context })
461
+ }
462
+
463
+ /**
464
+ * Extract error message from unknown error
465
+ */
466
+ export function getErrorMessage(error: unknown): string {
467
+ if (error instanceof Error) {
468
+ return error.message
469
+ }
470
+ return String(error)
471
+ }