@closeloop/sdk 0.1.7 → 0.1.9

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/dist/index.mjs CHANGED
@@ -1,12 +1,14 @@
1
1
  // src/constants.ts
2
2
  var DEFAULT_BASE_URL = "https://closeloop.app";
3
3
  var DEFAULT_TIMEOUT = 3e4;
4
- var SDK_VERSION = "0.1.0";
4
+ var SDK_VERSION = true ? "0.1.9" : "0.0.0";
5
5
  var USER_AGENT = `closeloop-node/${SDK_VERSION}`;
6
+ var WEBHOOK_TIMESTAMP_TOLERANCE_SECONDS = 300;
6
7
 
7
8
  // src/schemas/validation.ts
8
9
  import { z } from "zod";
9
10
  var walletAddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum wallet address");
11
+ var productIdSchema = z.string().min(1, "Product ID is required").max(100, "Product ID too long");
10
12
  var planIdSchema = z.string().min(1, "Plan ID is required").max(100, "Plan ID too long");
11
13
  var balanceIdSchema = z.string().min(1, "Balance ID is required").max(100, "Balance ID too long");
12
14
  var creditAmountSchema = z.number().int("Amount must be an integer").positive("Amount must be positive").max(1e9, "Amount exceeds maximum");
@@ -18,43 +20,43 @@ var transactionTypeSchema = z.enum([
18
20
  "EXPIRATION"
19
21
  ]);
20
22
  var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
21
- function sanitizeMetadata(metadata) {
23
+ var MAX_METADATA_DEPTH = 5;
24
+ function sanitizeMetadata(metadata, depth = 0) {
25
+ if (depth > MAX_METADATA_DEPTH) return void 0;
22
26
  if (!metadata) return void 0;
23
27
  const clean = /* @__PURE__ */ Object.create(null);
24
28
  for (const [key, value] of Object.entries(metadata)) {
25
29
  if (DANGEROUS_KEYS.has(key)) continue;
26
- clean[key] = value;
30
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
31
+ const sanitizedNested = sanitizeMetadata(
32
+ value,
33
+ depth + 1
34
+ );
35
+ if (sanitizedNested !== void 0) {
36
+ clean[key] = sanitizedNested;
37
+ }
38
+ } else {
39
+ clean[key] = value;
40
+ }
27
41
  }
28
42
  return Object.freeze(clean);
29
43
  }
30
44
  var verifyCreditsSchema = z.object({
31
45
  walletAddress: walletAddressSchema,
32
- planId: planIdSchema,
33
- amount: creditAmountSchema
46
+ amount: creditAmountSchema,
47
+ productId: productIdSchema
34
48
  });
35
49
  var consumeCreditsSchema = z.object({
50
+ productId: productIdSchema,
36
51
  walletAddress: walletAddressSchema,
37
- planId: planIdSchema,
38
52
  amount: creditAmountSchema,
39
53
  consumedBy: z.string().max(100).optional(),
40
54
  metadata: z.record(z.string(), z.unknown()).optional(),
41
55
  idempotencyKey: z.string().max(100).optional()
42
56
  });
43
- var batchConsumeSchema = z.object({
44
- operations: z.array(
45
- z.object({
46
- walletAddress: walletAddressSchema,
47
- planId: planIdSchema,
48
- amount: creditAmountSchema,
49
- consumedBy: z.string().max(100).optional(),
50
- metadata: z.record(z.string(), z.unknown()).optional()
51
- })
52
- ).min(1, "At least one operation required").max(100, "Maximum 100 operations per batch"),
53
- atomic: z.boolean().optional()
54
- });
55
57
  var getBalanceSchema = z.object({
56
- walletAddress: walletAddressSchema,
57
- planId: planIdSchema
58
+ productId: productIdSchema,
59
+ walletAddress: walletAddressSchema
58
60
  });
59
61
  var listBalancesSchema = z.object({
60
62
  walletAddress: walletAddressSchema,
@@ -68,6 +70,10 @@ var listTransactionsSchema = z.object({
68
70
  limit: paginationLimitSchema.optional(),
69
71
  cursor: z.string().max(200).optional()
70
72
  });
73
+ var getStatsSchema = z.object({
74
+ productId: productIdSchema,
75
+ walletAddress: walletAddressSchema
76
+ });
71
77
  var webhookEventTypeSchema = z.enum([
72
78
  "payment.success",
73
79
  "credits.low",
@@ -81,21 +87,20 @@ var webhookEventSchema = z.object({
81
87
  });
82
88
  var paymentSuccessPayloadSchema = z.object({
83
89
  type: z.string(),
90
+ productId: z.string(),
84
91
  planId: z.string(),
85
- planName: z.string(),
86
- price: z.number(),
87
- walletAddress: z.string(),
88
- transactionId: z.string()
92
+ transactionId: z.string(),
93
+ walletAddress: z.string()
89
94
  }).loose();
90
95
  var creditsLowPayloadSchema = z.object({
96
+ productId: z.string(),
91
97
  walletAddress: z.string(),
92
- planId: z.string(),
93
98
  remainingCredits: z.number(),
94
99
  threshold: z.number()
95
100
  });
96
101
  var creditsExpiredPayloadSchema = z.object({
102
+ productId: z.string(),
97
103
  walletAddress: z.string(),
98
- planId: z.string(),
99
104
  expiredCredits: z.number(),
100
105
  expiresAt: z.string()
101
106
  });
@@ -167,6 +172,17 @@ var ValidationError = class extends CloseLoopError {
167
172
  }
168
173
  };
169
174
 
175
+ // src/utils/query.ts
176
+ function buildQueryString(params) {
177
+ const query = new URLSearchParams();
178
+ for (const [key, value] of Object.entries(params)) {
179
+ if (value !== void 0) {
180
+ query.set(key, String(value));
181
+ }
182
+ }
183
+ return query.toString();
184
+ }
185
+
170
186
  // src/utils/validation.ts
171
187
  function validateInput(schema, data) {
172
188
  const result = schema.safeParse(data);
@@ -196,8 +212,7 @@ var Balances = class {
196
212
  * @example
197
213
  * ```typescript
198
214
  * const balance = await client.balances.get({
199
- * walletAddress: "0x1234...",
200
- * planId: "plan_abc123"
215
+ * walletAddress: "0x1234..."
201
216
  * })
202
217
  *
203
218
  * if (balance) {
@@ -210,9 +225,9 @@ var Balances = class {
210
225
  */
211
226
  async get(params) {
212
227
  const validated = validateInput(getBalanceSchema, params);
213
- const query = this.buildQueryString({
228
+ const query = buildQueryString({
214
229
  walletAddress: validated.walletAddress,
215
- planId: validated.planId
230
+ productId: validated.productId
216
231
  });
217
232
  try {
218
233
  return await this.http.request({
@@ -238,7 +253,7 @@ var Balances = class {
238
253
  * })
239
254
  *
240
255
  * for (const balance of balances) {
241
- * console.log(`${balance.planName}: ${balance.remainingCredits} credits`)
256
+ * console.log(`${balance.totalCredits}: ${balance.remainingCredits} credits`)
242
257
  * }
243
258
  *
244
259
  * // Paginate if needed
@@ -254,7 +269,7 @@ var Balances = class {
254
269
  */
255
270
  async list(params) {
256
271
  const validated = validateInput(listBalancesSchema, params);
257
- const query = this.buildQueryString({
272
+ const query = buildQueryString({
258
273
  walletAddress: validated.walletAddress,
259
274
  activeOnly: validated.activeOnly,
260
275
  limit: validated.limit,
@@ -285,7 +300,7 @@ var Balances = class {
285
300
  */
286
301
  async transactions(params) {
287
302
  const validated = validateInput(listTransactionsSchema, params);
288
- const query = this.buildQueryString({
303
+ const query = buildQueryString({
289
304
  type: validated.type,
290
305
  limit: validated.limit,
291
306
  cursor: validated.cursor
@@ -302,42 +317,32 @@ var Balances = class {
302
317
  *
303
318
  * @example
304
319
  * ```typescript
305
- * const stats = await client.balances.stats("0x1234...")
320
+ * const stats = await client.balances.stats({
321
+ * walletAddress: "0x1234...",
322
+ * productId: "prod_123"
323
+ * })
306
324
  * console.log(`Total: ${stats.totalCredits}, Used: ${stats.totalUsed}`)
307
325
  * ```
308
326
  *
309
327
  * @throws {CloseLoopError} When input validation fails
310
328
  */
311
- async stats(walletAddress) {
312
- validateInput(walletAddressSchema, walletAddress);
313
- const query = this.buildQueryString({ walletAddress });
329
+ async stats(params) {
330
+ validateInput(getStatsSchema, params);
331
+ const query = buildQueryString({
332
+ walletAddress: params.walletAddress,
333
+ productId: params.productId
334
+ });
314
335
  return this.http.request({
315
336
  method: "GET",
316
337
  path: `${ENDPOINTS.STATS}?${query}`
317
338
  });
318
339
  }
319
- // ==========================================================================
320
- // Private Helpers
321
- // ==========================================================================
322
- /**
323
- * Build URL query string from params, filtering out undefined values
324
- */
325
- buildQueryString(params) {
326
- const query = new URLSearchParams();
327
- for (const [key, value] of Object.entries(params)) {
328
- if (value !== void 0) {
329
- query.set(key, String(value));
330
- }
331
- }
332
- return query.toString();
333
- }
334
340
  };
335
341
 
336
342
  // src/resources/credits.ts
337
343
  var ENDPOINTS2 = {
338
344
  VERIFY: "/api/credit/verify",
339
- CONSUME: "/api/credit/consume",
340
- BATCH_CONSUME: "/api/credit/consume/batch"
345
+ CONSUME: "/api/credit/consume"
341
346
  };
342
347
  var Credits = class {
343
348
  constructor(http) {
@@ -350,11 +355,10 @@ var Credits = class {
350
355
  * ```typescript
351
356
  * const result = await client.credits.verify({
352
357
  * walletAddress: "0x1234...",
353
- * planId: "plan_abc123",
354
358
  * amount: 10
355
359
  * })
356
360
  *
357
- * if (result.hasEnoughCredits) {
361
+ * if (result.hasEnough) {
358
362
  * // Proceed with service
359
363
  * }
360
364
  * ```
@@ -376,7 +380,6 @@ var Credits = class {
376
380
  * ```typescript
377
381
  * const result = await client.credits.consume({
378
382
  * walletAddress: "0x1234...",
379
- * planId: "plan_abc123",
380
383
  * amount: 1,
381
384
  * consumedBy: "ai-generation",
382
385
  * metadata: { requestId: "req_xyz" }
@@ -399,18 +402,22 @@ var Credits = class {
399
402
  });
400
403
  }
401
404
  /**
402
- * Verify and consume credits in a single atomic operation.
403
- * This is useful when you want to ensure credits are available
404
- * before consuming them, without race conditions.
405
+ * Verify and consume credits in a two-step operation.
406
+ *
407
+ * **Important**: This method performs TWO separate API calls (verify, then consume).
408
+ * There is a potential race condition between the verify and consume steps where
409
+ * another request could consume credits. For critical operations, consider:
410
+ * - Using an `idempotencyKey` to prevent duplicate consumption
411
+ * - Implementing server-side locking if your use case requires strict atomicity
405
412
  *
406
413
  * @example
407
414
  * ```typescript
408
415
  * try {
409
416
  * const result = await client.credits.verifyAndConsume({
410
417
  * walletAddress: "0x1234...",
411
- * planId: "plan_abc123",
412
418
  * amount: 5,
413
- * consumedBy: "batch-processing"
419
+ * consumedBy: "batch-processing",
420
+ * idempotencyKey: "unique-request-id" // Recommended for safety
414
421
  * })
415
422
  * // Credits were verified and consumed
416
423
  * } catch (error) {
@@ -431,8 +438,8 @@ var Credits = class {
431
438
  path: ENDPOINTS2.VERIFY,
432
439
  body: {
433
440
  walletAddress: validated.walletAddress,
434
- planId: validated.planId,
435
- amount: validated.amount
441
+ amount: validated.amount,
442
+ productId: validated.productId
436
443
  }
437
444
  });
438
445
  this.assertCreditsAvailable(verification, validated.amount);
@@ -443,39 +450,6 @@ var Credits = class {
443
450
  headers: this.buildIdempotencyHeaders(validated.idempotencyKey)
444
451
  });
445
452
  }
446
- /**
447
- * Consume credits in batch.
448
- *
449
- * @example
450
- * ```typescript
451
- * const result = await client.credits.batchConsume({
452
- * operations: [
453
- * { walletAddress: "0x1234...", planId: "plan_a", amount: 1 },
454
- * { walletAddress: "0x5678...", planId: "plan_b", amount: 2 }
455
- * ],
456
- * atomic: false // Allow partial success
457
- * })
458
- *
459
- * console.log(`Success: ${result.successCount}, Failed: ${result.failedCount}`)
460
- * ```
461
- *
462
- * @throws {CloseLoopError} When input validation fails
463
- */
464
- async batchConsume(params) {
465
- const validated = validateInput(batchConsumeSchema, params);
466
- const sanitizedOperations = validated.operations.map((op) => ({
467
- ...op,
468
- metadata: sanitizeMetadata(op.metadata)
469
- }));
470
- return this.http.request({
471
- method: "POST",
472
- path: ENDPOINTS2.BATCH_CONSUME,
473
- body: {
474
- operations: sanitizedOperations,
475
- atomic: validated.atomic
476
- }
477
- });
478
- }
479
453
  // ==========================================================================
480
454
  // Private Helpers
481
455
  // ==========================================================================
@@ -485,8 +459,8 @@ var Credits = class {
485
459
  buildConsumeBody(validated) {
486
460
  return {
487
461
  walletAddress: validated.walletAddress,
488
- planId: validated.planId,
489
462
  amount: validated.amount,
463
+ productId: validated.productId,
490
464
  consumedBy: validated.consumedBy,
491
465
  metadata: sanitizeMetadata(validated.metadata)
492
466
  };
@@ -534,7 +508,8 @@ function verifySignature(payload, signature, secret) {
534
508
  var ERROR_CODES = {
535
509
  VALIDATION: "VALIDATION_ERROR",
536
510
  INVALID_SIGNATURE: "INVALID_SIGNATURE",
537
- INVALID_PAYLOAD: "INVALID_PAYLOAD"
511
+ INVALID_PAYLOAD: "INVALID_PAYLOAD",
512
+ TIMESTAMP_EXPIRED: "TIMESTAMP_EXPIRED"
538
513
  };
539
514
  var Webhooks = class {
540
515
  /**
@@ -588,7 +563,10 @@ var Webhooks = class {
588
563
  const payloadString = this.normalizePayload(params.payload);
589
564
  this.verifySignatureOrThrow(payloadString, params.signature, params.secret);
590
565
  const parsed = this.parseJsonOrThrow(payloadString);
591
- return this.validateEventStructure(parsed);
566
+ const event = this.validateEventStructure(parsed);
567
+ const tolerance = params.toleranceSeconds ?? WEBHOOK_TIMESTAMP_TOLERANCE_SECONDS;
568
+ this.validateTimestamp(event.createdAt, tolerance);
569
+ return event;
592
570
  }
593
571
  /**
594
572
  * Type guard for payment.success events
@@ -708,7 +686,29 @@ var Webhooks = class {
708
686
  400
709
687
  );
710
688
  }
711
- return result.data;
689
+ const validatedData = result.data;
690
+ return {
691
+ id: validatedData.id,
692
+ type: validatedData.type,
693
+ createdAt: validatedData.createdAt,
694
+ data: validatedData.data
695
+ };
696
+ }
697
+ /**
698
+ * Validate webhook timestamp to prevent replay attacks
699
+ * Rejects webhooks older than the specified tolerance
700
+ */
701
+ validateTimestamp(createdAt, toleranceSeconds) {
702
+ const eventTime = new Date(createdAt).getTime();
703
+ const now = Date.now();
704
+ const ageSeconds = (now - eventTime) / 1e3;
705
+ if (ageSeconds > toleranceSeconds || ageSeconds < -60) {
706
+ throw new CloseLoopError(
707
+ `Webhook timestamp expired or invalid. Event age: ${Math.round(ageSeconds)}s, tolerance: ${toleranceSeconds}s`,
708
+ ERROR_CODES.TIMESTAMP_EXPIRED,
709
+ 400
710
+ );
711
+ }
712
712
  }
713
713
  };
714
714
 
@@ -809,8 +809,10 @@ var CloseLoop = class {
809
809
  */
810
810
  webhooks;
811
811
  constructor(options) {
812
- if (!options.apiKey) {
813
- throw new Error("CloseLoop API key is required");
812
+ if (!options.apiKey || typeof options.apiKey !== "string" || options.apiKey.trim().length < 10) {
813
+ throw new AuthenticationError(
814
+ "CloseLoop API key is required and must be at least 10 characters"
815
+ );
814
816
  }
815
817
  const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
816
818
  this.httpClient = new HttpClient({
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/constants.ts","../src/schemas/validation.ts","../src/utils/errors.ts","../src/utils/validation.ts","../src/resources/balances.ts","../src/resources/credits.ts","../src/utils/crypto.ts","../src/resources/webhooks.ts","../src/utils/http.ts","../src/client.ts"],"sourcesContent":["export const DEFAULT_BASE_URL = \"https://closeloop.app\"\nexport const DEFAULT_TIMEOUT = 30000 // 30 seconds\nexport const SDK_VERSION = \"0.1.0\"\nexport const USER_AGENT = `closeloop-node/${SDK_VERSION}`\n","import { z } from \"zod\"\n\n/**\n * Ethereum wallet address validation (0x + 40 hex chars)\n */\nexport const walletAddressSchema = z\n .string()\n .regex(/^0x[a-fA-F0-9]{40}$/, \"Invalid Ethereum wallet address\")\n\n/**\n * Plan ID validation (non-empty string with reasonable length)\n */\nexport const planIdSchema = z\n .string()\n .min(1, \"Plan ID is required\")\n .max(100, \"Plan ID too long\")\n\n/**\n * Balance ID validation\n */\nexport const balanceIdSchema = z\n .string()\n .min(1, \"Balance ID is required\")\n .max(100, \"Balance ID too long\")\n\n/**\n * Credit amount validation (positive integer with reasonable max)\n */\nexport const creditAmountSchema = z\n .number()\n .int(\"Amount must be an integer\")\n .positive(\"Amount must be positive\")\n .max(1_000_000_000, \"Amount exceeds maximum\")\n\n/**\n * Pagination limit validation\n */\nexport const paginationLimitSchema = z\n .number()\n .int()\n .positive()\n .max(100, \"Limit cannot exceed 100\")\n\n/**\n * Transaction type validation\n */\nexport const transactionTypeSchema = z.enum([\n \"PURCHASE\",\n \"CONSUMPTION\",\n \"REFUND\",\n \"EXPIRATION\"\n])\n\n/**\n * Dangerous prototype pollution keys to skip\n */\nconst DANGEROUS_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"])\n\n/**\n * Sanitize metadata object to prevent prototype pollution\n * Creates a clean object with no prototype chain\n */\nexport function sanitizeMetadata(\n metadata: Record<string, unknown> | undefined\n): Record<string, unknown> | undefined {\n if (!metadata) return undefined\n\n // Create a clean object with null prototype to prevent prototype pollution\n const clean = Object.create(null) as Record<string, unknown>\n\n for (const [key, value] of Object.entries(metadata)) {\n // Skip dangerous prototype pollution keys\n if (DANGEROUS_KEYS.has(key)) continue\n\n // Copy all JSON-serializable values\n clean[key] = value\n }\n\n return Object.freeze(clean)\n}\n\n// ============================================================================\n// Credit Schemas\n// ============================================================================\n\nexport const verifyCreditsSchema = z.object({\n walletAddress: walletAddressSchema,\n planId: planIdSchema,\n amount: creditAmountSchema\n})\n\nexport const consumeCreditsSchema = z.object({\n walletAddress: walletAddressSchema,\n planId: planIdSchema,\n amount: creditAmountSchema,\n consumedBy: z.string().max(100).optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n idempotencyKey: z.string().max(100).optional()\n})\n\nexport const batchConsumeSchema = z.object({\n operations: z\n .array(\n z.object({\n walletAddress: walletAddressSchema,\n planId: planIdSchema,\n amount: creditAmountSchema,\n consumedBy: z.string().max(100).optional(),\n metadata: z.record(z.string(), z.unknown()).optional()\n })\n )\n .min(1, \"At least one operation required\")\n .max(100, \"Maximum 100 operations per batch\"),\n atomic: z.boolean().optional()\n})\n\n// ============================================================================\n// Balance Schemas\n// ============================================================================\n\nexport const getBalanceSchema = z.object({\n walletAddress: walletAddressSchema,\n planId: planIdSchema\n})\n\nexport const listBalancesSchema = z.object({\n walletAddress: walletAddressSchema,\n activeOnly: z.boolean().optional(),\n limit: paginationLimitSchema.optional(),\n cursor: z.string().max(200).optional()\n})\n\nexport const listTransactionsSchema = z.object({\n balanceId: balanceIdSchema,\n type: transactionTypeSchema.optional(),\n limit: paginationLimitSchema.optional(),\n cursor: z.string().max(200).optional()\n})\n\n// ============================================================================\n// Webhook Schemas\n// ============================================================================\n\nexport const webhookEventTypeSchema = z.enum([\n \"payment.success\",\n \"credits.low\",\n \"credits.expired\"\n])\n\nexport const webhookEventSchema = z.object({\n type: webhookEventTypeSchema,\n id: z.string().min(1),\n createdAt: z.string(),\n data: z.record(z.string(), z.unknown())\n})\n\nexport const paymentSuccessPayloadSchema = z\n .object({\n type: z.string(),\n planId: z.string(),\n planName: z.string(),\n price: z.number(),\n walletAddress: z.string(),\n transactionId: z.string()\n })\n .loose()\n\nexport const creditsLowPayloadSchema = z.object({\n walletAddress: z.string(),\n planId: z.string(),\n remainingCredits: z.number(),\n threshold: z.number()\n})\n\nexport const creditsExpiredPayloadSchema = z.object({\n walletAddress: z.string(),\n planId: z.string(),\n expiredCredits: z.number(),\n expiresAt: z.string()\n})\n","/**\n * Base error class for all CloseLoop SDK errors\n */\nexport class CloseLoopError extends Error {\n constructor(\n message: string,\n public code: string,\n public statusCode?: number,\n public details?: Record<string, unknown>\n ) {\n super(message)\n this.name = \"CloseLoopError\"\n }\n}\n\n/**\n * Thrown when a user doesn't have enough credits for an operation\n */\nexport class InsufficientCreditsError extends CloseLoopError {\n constructor(\n public remainingCredits: number,\n public requiredCredits: number\n ) {\n super(\n `Insufficient credits: ${remainingCredits} available, ${requiredCredits} required`,\n \"INSUFFICIENT_CREDITS\",\n 402\n )\n this.name = \"InsufficientCreditsError\"\n }\n}\n\n/**\n * Thrown when credits have expired\n */\nexport class CreditsExpiredError extends CloseLoopError {\n constructor(public expiresAt: Date) {\n super(\n `Credits expired at ${expiresAt.toISOString()}`,\n \"CREDITS_EXPIRED\",\n 410\n )\n this.name = \"CreditsExpiredError\"\n }\n}\n\n/**\n * Thrown when authentication fails (invalid API key)\n */\nexport class AuthenticationError extends CloseLoopError {\n constructor(message = \"Invalid API key\") {\n super(message, \"AUTHENTICATION_ERROR\", 401)\n this.name = \"AuthenticationError\"\n }\n}\n\n/**\n * Thrown when rate limit is exceeded\n */\nexport class RateLimitError extends CloseLoopError {\n constructor(public retryAfter?: number) {\n super(\"Rate limit exceeded\", \"RATE_LIMIT\", 429)\n this.name = \"RateLimitError\"\n }\n}\n\n/**\n * Thrown when a network error occurs\n */\nexport class NetworkError extends CloseLoopError {\n constructor(\n message: string,\n public cause?: Error\n ) {\n super(message, \"NETWORK_ERROR\")\n this.name = \"NetworkError\"\n }\n}\n\n/**\n * Thrown when a resource is not found\n */\nexport class NotFoundError extends CloseLoopError {\n constructor(resource: string) {\n super(`${resource} not found`, \"NOT_FOUND\", 404)\n this.name = \"NotFoundError\"\n }\n}\n\n/**\n * Thrown when input validation fails\n */\nexport class ValidationError extends CloseLoopError {\n constructor(\n message: string,\n public field?: string\n ) {\n super(message, \"VALIDATION_ERROR\", 400)\n this.name = \"ValidationError\"\n }\n}\n","import { CloseLoopError } from \"./errors\"\n\n/**\n * Generic schema interface for validation\n */\nexport interface ValidationSchema<T> {\n safeParse: (data: unknown) => {\n success: boolean\n data?: T\n error?: { message: string }\n }\n}\n\n/**\n * Validate input against a Zod schema\n * Throws CloseLoopError if validation fails\n *\n * @param schema - Zod schema to validate against\n * @param data - Data to validate\n * @returns Validated and typed data\n * @throws {CloseLoopError} When validation fails\n */\nexport function validateInput<T>(\n schema: ValidationSchema<T>,\n data: unknown\n): T {\n const result = schema.safeParse(data)\n\n if (!result.success) {\n throw new CloseLoopError(\n `Validation error: ${result.error?.message || \"Invalid input\"}`,\n \"VALIDATION_ERROR\",\n 400\n )\n }\n\n return result.data as T\n}\n\n/**\n * Validate a single value against a Zod schema\n * Returns a boolean without throwing\n */\nexport function isValid<T>(\n schema: ValidationSchema<T>,\n data: unknown\n): data is T {\n return schema.safeParse(data).success\n}\n","import {\n getBalanceSchema,\n listBalancesSchema,\n listTransactionsSchema,\n walletAddressSchema\n} from \"../schemas/validation\"\nimport type {\n CreditBalance,\n CreditStats,\n GetBalanceParams,\n ListBalancesParams,\n ListBalancesResponse,\n ListTransactionsParams,\n ListTransactionsResponse\n} from \"../types/balances\"\nimport { NotFoundError } from \"../utils/errors\"\nimport { HttpClient } from \"../utils/http\"\nimport { validateInput } from \"../utils/validation\"\n\n// API Endpoints\nconst ENDPOINTS = {\n BALANCE: \"/api/credit/balance\",\n BALANCES: \"/api/credit/balances\",\n STATS: \"/api/credit/stats\"\n} as const\n\n/**\n * Resource for querying credit balances and transaction history\n */\nexport class Balances {\n constructor(private readonly http: HttpClient) {}\n\n /**\n * Get a specific credit balance for a wallet and plan.\n *\n * @example\n * ```typescript\n * const balance = await client.balances.get({\n * walletAddress: \"0x1234...\",\n * planId: \"plan_abc123\"\n * })\n *\n * if (balance) {\n * console.log(`Credits: ${balance.remainingCredits}/${balance.totalCredits}`)\n * }\n * ```\n *\n * @returns The credit balance, or null if not found\n * @throws {CloseLoopError} When input validation fails\n */\n async get(params: GetBalanceParams): Promise<CreditBalance | null> {\n const validated = validateInput(getBalanceSchema, params)\n\n const query = this.buildQueryString({\n walletAddress: validated.walletAddress,\n planId: validated.planId\n })\n\n try {\n return await this.http.request<CreditBalance>({\n method: \"GET\",\n path: `${ENDPOINTS.BALANCE}?${query}`\n })\n } catch (error) {\n if (error instanceof NotFoundError) {\n return null\n }\n throw error\n }\n }\n\n /**\n * List all credit balances for a wallet.\n *\n * @example\n * ```typescript\n * const { balances, nextCursor } = await client.balances.list({\n * walletAddress: \"0x1234...\",\n * activeOnly: true,\n * limit: 10\n * })\n *\n * for (const balance of balances) {\n * console.log(`${balance.planName}: ${balance.remainingCredits} credits`)\n * }\n *\n * // Paginate if needed\n * if (nextCursor) {\n * const nextPage = await client.balances.list({\n * walletAddress: \"0x1234...\",\n * cursor: nextCursor\n * })\n * }\n * ```\n *\n * @throws {CloseLoopError} When input validation fails\n */\n async list(params: ListBalancesParams): Promise<ListBalancesResponse> {\n const validated = validateInput(listBalancesSchema, params)\n\n const query = this.buildQueryString({\n walletAddress: validated.walletAddress,\n activeOnly: validated.activeOnly,\n limit: validated.limit,\n cursor: validated.cursor\n })\n\n return this.http.request<ListBalancesResponse>({\n method: \"GET\",\n path: `${ENDPOINTS.BALANCES}?${query}`\n })\n }\n\n /**\n * Get transaction history for a balance.\n *\n * @example\n * ```typescript\n * const { transactions } = await client.balances.transactions({\n * balanceId: \"bal_xyz\",\n * type: \"CONSUMPTION\",\n * limit: 50\n * })\n *\n * for (const tx of transactions) {\n * console.log(`${tx.type}: ${tx.amount} - ${tx.description}`)\n * }\n * ```\n *\n * @throws {CloseLoopError} When input validation fails\n */\n async transactions(\n params: ListTransactionsParams\n ): Promise<ListTransactionsResponse> {\n const validated = validateInput(listTransactionsSchema, params)\n\n const query = this.buildQueryString({\n type: validated.type,\n limit: validated.limit,\n cursor: validated.cursor\n })\n\n const basePath = `${ENDPOINTS.BALANCE}/${encodeURIComponent(validated.balanceId)}/transactions`\n const path = query ? `${basePath}?${query}` : basePath\n\n return this.http.request<ListTransactionsResponse>({\n method: \"GET\",\n path\n })\n }\n\n /**\n * Get aggregated stats for a wallet's credits.\n *\n * @example\n * ```typescript\n * const stats = await client.balances.stats(\"0x1234...\")\n * console.log(`Total: ${stats.totalCredits}, Used: ${stats.totalUsed}`)\n * ```\n *\n * @throws {CloseLoopError} When input validation fails\n */\n async stats(walletAddress: string): Promise<CreditStats> {\n // Validate using the shared utility for consistency\n validateInput(walletAddressSchema, walletAddress)\n\n const query = this.buildQueryString({ walletAddress })\n\n return this.http.request<CreditStats>({\n method: \"GET\",\n path: `${ENDPOINTS.STATS}?${query}`\n })\n }\n\n // ==========================================================================\n // Private Helpers\n // ==========================================================================\n\n /**\n * Build URL query string from params, filtering out undefined values\n */\n private buildQueryString(\n params: Record<string, string | number | boolean | undefined>\n ): string {\n const query = new URLSearchParams()\n\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) {\n query.set(key, String(value))\n }\n }\n\n return query.toString()\n }\n}\n","import {\n batchConsumeSchema,\n consumeCreditsSchema,\n sanitizeMetadata,\n verifyCreditsSchema\n} from \"../schemas/validation\"\nimport type {\n BatchConsumeParams,\n BatchConsumeResponse,\n ConsumeCreditsParams,\n ConsumeCreditsResponse,\n VerifyCreditsParams,\n VerifyCreditsResponse\n} from \"../types/credits\"\nimport { CreditsExpiredError, InsufficientCreditsError } from \"../utils/errors\"\nimport { HttpClient } from \"../utils/http\"\nimport { validateInput } from \"../utils/validation\"\n\n// API Endpoints\nconst ENDPOINTS = {\n VERIFY: \"/api/credit/verify\",\n CONSUME: \"/api/credit/consume\",\n BATCH_CONSUME: \"/api/credit/consume/batch\"\n} as const\n\n/**\n * Resource for credit verification and consumption operations\n */\nexport class Credits {\n constructor(private readonly http: HttpClient) {}\n\n /**\n * Verify if a user has enough credits without consuming them.\n *\n * @example\n * ```typescript\n * const result = await client.credits.verify({\n * walletAddress: \"0x1234...\",\n * planId: \"plan_abc123\",\n * amount: 10\n * })\n *\n * if (result.hasEnoughCredits) {\n * // Proceed with service\n * }\n * ```\n *\n * @throws {CloseLoopError} When input validation fails\n */\n async verify(params: VerifyCreditsParams): Promise<VerifyCreditsResponse> {\n const validated = validateInput(verifyCreditsSchema, params)\n\n return this.http.request<VerifyCreditsResponse>({\n method: \"POST\",\n path: ENDPOINTS.VERIFY,\n body: validated\n })\n }\n\n /**\n * Consume credits from a user's balance.\n *\n * @example\n * ```typescript\n * const result = await client.credits.consume({\n * walletAddress: \"0x1234...\",\n * planId: \"plan_abc123\",\n * amount: 1,\n * consumedBy: \"ai-generation\",\n * metadata: { requestId: \"req_xyz\" }\n * })\n *\n * console.log(`Remaining: ${result.remainingCredits}`)\n * ```\n *\n * @throws {InsufficientCreditsError} When user doesn't have enough credits\n * @throws {CreditsExpiredError} When credits have expired\n * @throws {CloseLoopError} When input validation fails\n */\n async consume(params: ConsumeCreditsParams): Promise<ConsumeCreditsResponse> {\n const validated = validateInput(consumeCreditsSchema, params)\n\n return this.http.request<ConsumeCreditsResponse>({\n method: \"POST\",\n path: ENDPOINTS.CONSUME,\n body: this.buildConsumeBody(validated),\n headers: this.buildIdempotencyHeaders(validated.idempotencyKey)\n })\n }\n\n /**\n * Verify and consume credits in a single atomic operation.\n * This is useful when you want to ensure credits are available\n * before consuming them, without race conditions.\n *\n * @example\n * ```typescript\n * try {\n * const result = await client.credits.verifyAndConsume({\n * walletAddress: \"0x1234...\",\n * planId: \"plan_abc123\",\n * amount: 5,\n * consumedBy: \"batch-processing\"\n * })\n * // Credits were verified and consumed\n * } catch (error) {\n * if (error instanceof InsufficientCreditsError) {\n * // Handle insufficient credits\n * }\n * }\n * ```\n *\n * @throws {InsufficientCreditsError} When user doesn't have enough credits\n * @throws {CreditsExpiredError} When credits have expired\n * @throws {CloseLoopError} When input validation fails\n */\n async verifyAndConsume(\n params: ConsumeCreditsParams\n ): Promise<ConsumeCreditsResponse> {\n const validated = validateInput(consumeCreditsSchema, params)\n\n // First verify\n const verification = await this.http.request<VerifyCreditsResponse>({\n method: \"POST\",\n path: ENDPOINTS.VERIFY,\n body: {\n walletAddress: validated.walletAddress,\n planId: validated.planId,\n amount: validated.amount\n }\n })\n\n this.assertCreditsAvailable(verification, validated.amount)\n\n // Then consume\n return this.http.request<ConsumeCreditsResponse>({\n method: \"POST\",\n path: ENDPOINTS.CONSUME,\n body: this.buildConsumeBody(validated),\n headers: this.buildIdempotencyHeaders(validated.idempotencyKey)\n })\n }\n\n /**\n * Consume credits in batch.\n *\n * @example\n * ```typescript\n * const result = await client.credits.batchConsume({\n * operations: [\n * { walletAddress: \"0x1234...\", planId: \"plan_a\", amount: 1 },\n * { walletAddress: \"0x5678...\", planId: \"plan_b\", amount: 2 }\n * ],\n * atomic: false // Allow partial success\n * })\n *\n * console.log(`Success: ${result.successCount}, Failed: ${result.failedCount}`)\n * ```\n *\n * @throws {CloseLoopError} When input validation fails\n */\n async batchConsume(\n params: BatchConsumeParams\n ): Promise<BatchConsumeResponse> {\n const validated = validateInput(batchConsumeSchema, params)\n\n // Sanitize metadata in each operation\n const sanitizedOperations = validated.operations.map(op => ({\n ...op,\n metadata: sanitizeMetadata(op.metadata)\n }))\n\n return this.http.request<BatchConsumeResponse>({\n method: \"POST\",\n path: ENDPOINTS.BATCH_CONSUME,\n body: {\n operations: sanitizedOperations,\n atomic: validated.atomic\n }\n })\n }\n\n // ==========================================================================\n // Private Helpers\n // ==========================================================================\n\n /**\n * Build consumption request body with sanitized metadata\n */\n private buildConsumeBody(validated: {\n walletAddress: string\n planId: string\n amount: number\n consumedBy?: string\n metadata?: Record<string, unknown>\n }) {\n return {\n walletAddress: validated.walletAddress,\n planId: validated.planId,\n amount: validated.amount,\n consumedBy: validated.consumedBy,\n metadata: sanitizeMetadata(validated.metadata)\n }\n }\n\n /**\n * Build idempotency headers if key is provided\n */\n private buildIdempotencyHeaders(\n idempotencyKey?: string\n ): Record<string, string> {\n if (!idempotencyKey) return {}\n return { \"Idempotency-Key\": idempotencyKey }\n }\n\n /**\n * Assert credits are available, throw typed errors if not\n */\n private assertCreditsAvailable(\n verification: VerifyCreditsResponse,\n requiredAmount: number\n ): void {\n if (!verification.hasEnoughCredits) {\n throw new InsufficientCreditsError(\n verification.remainingCredits,\n requiredAmount\n )\n }\n\n if (verification.expiresAt) {\n const expirationDate = new Date(verification.expiresAt)\n if (expirationDate < new Date()) {\n throw new CreditsExpiredError(expirationDate)\n }\n }\n }\n}\n","import { createHmac, timingSafeEqual } from \"crypto\"\n\n/**\n * Generates an HMAC signature for webhook verification\n */\nexport function generateSignature(payload: string, secret: string): string {\n return createHmac(\"sha256\", secret).update(payload).digest(\"hex\")\n}\n\n/**\n * Verifies that a webhook signature is valid using timing-safe comparison\n */\nexport function verifySignature(\n payload: string,\n signature: string,\n secret: string\n): boolean {\n const expectedSignature = generateSignature(payload, secret)\n\n // Use timing-safe comparison to prevent timing attacks\n if (signature.length !== expectedSignature.length) {\n return false\n }\n\n return timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))\n}\n","import {\n creditsExpiredPayloadSchema,\n creditsLowPayloadSchema,\n paymentSuccessPayloadSchema,\n webhookEventSchema\n} from \"../schemas/validation\"\nimport type {\n CreditsExpiredPayload,\n CreditsLowPayload,\n PaymentSuccessPayload,\n WebhookEvent,\n WebhookPayload\n} from \"../types/webhooks\"\nimport { verifySignature } from \"../utils/crypto\"\nimport { CloseLoopError } from \"../utils/errors\"\n\n/**\n * Parameters for verifying a webhook\n */\nexport interface VerifyWebhookParams {\n /**\n * Raw request body as string or Buffer\n */\n payload: string | Buffer\n\n /**\n * X-CloseLoop-Signature header value\n */\n signature: string\n\n /**\n * Your webhook secret\n */\n secret: string\n}\n\n// Validation error codes\nconst ERROR_CODES = {\n VALIDATION: \"VALIDATION_ERROR\",\n INVALID_SIGNATURE: \"INVALID_SIGNATURE\",\n INVALID_PAYLOAD: \"INVALID_PAYLOAD\"\n} as const\n\n/**\n * Resource for webhook signature verification and event handling\n */\nexport class Webhooks {\n /**\n * Verify a webhook signature and parse the event.\n *\n * @example\n * ```typescript\n * // Express handler\n * app.post(\"/webhook\", (req, res) => {\n * try {\n * const event = client.webhooks.verify({\n * payload: req.body,\n * signature: req.headers[\"x-closeloop-signature\"],\n * secret: process.env.WEBHOOK_SECRET!\n * })\n *\n * if (client.webhooks.isPaymentSuccess(event)) {\n * const { type, walletAddress, planId, amount } = event.data\n * // Handle payment success\n * }\n *\n * res.json({ received: true })\n * } catch (error) {\n * res.status(400).json({ error: \"Invalid signature\" })\n * }\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Next.js App Router\n * export async function POST(request: Request) {\n * const payload = await request.text()\n * const signature = request.headers.get(\"x-closeloop-signature\")!\n *\n * const event = client.webhooks.verify({\n * payload,\n * signature,\n * secret: process.env.WEBHOOK_SECRET!\n * })\n *\n * // Handle event...\n * return Response.json({ received: true })\n * }\n * ```\n *\n * @throws {CloseLoopError} When signature is invalid or payload is malformed\n */\n verify(params: VerifyWebhookParams): WebhookEvent {\n this.validateRequiredParams(params)\n\n const payloadString = this.normalizePayload(params.payload)\n\n this.verifySignatureOrThrow(payloadString, params.signature, params.secret)\n\n const parsed = this.parseJsonOrThrow(payloadString)\n\n return this.validateEventStructure(parsed)\n }\n\n /**\n * Type guard for payment.success events\n * Also validates the payload structure\n *\n * @example\n * ```typescript\n * if (client.webhooks.isPaymentSuccess(event)) {\n * // TypeScript knows event.data is PaymentSuccessPayload\n * console.log(event.data.planId, event.data.walletAddress)\n * }\n * ```\n */\n isPaymentSuccess(\n event: WebhookEvent<WebhookPayload>\n ): event is WebhookEvent<PaymentSuccessPayload> {\n return (\n event.type === \"payment.success\" &&\n paymentSuccessPayloadSchema.safeParse(event.data).success\n )\n }\n\n /**\n * Type guard for credits.low events\n * Also validates the payload structure\n *\n * @example\n * ```typescript\n * if (client.webhooks.isCreditsLow(event)) {\n * // TypeScript knows event.data is CreditsLowPayload\n * console.log(`Low balance: ${event.data.remainingCredits}`)\n * }\n * ```\n */\n isCreditsLow(\n event: WebhookEvent<WebhookPayload>\n ): event is WebhookEvent<CreditsLowPayload> {\n return (\n event.type === \"credits.low\" &&\n creditsLowPayloadSchema.safeParse(event.data).success\n )\n }\n\n /**\n * Type guard for credits.expired events\n * Also validates the payload structure\n *\n * @example\n * ```typescript\n * if (client.webhooks.isCreditsExpired(event)) {\n * // TypeScript knows event.data is CreditsExpiredPayload\n * console.log(`Expired: ${event.data.expiredCredits} credits`)\n * }\n * ```\n */\n isCreditsExpired(\n event: WebhookEvent<WebhookPayload>\n ): event is WebhookEvent<CreditsExpiredPayload> {\n return (\n event.type === \"credits.expired\" &&\n creditsExpiredPayloadSchema.safeParse(event.data).success\n )\n }\n\n // ==========================================================================\n // Private Helpers\n // ==========================================================================\n\n /**\n * Validate that all required parameters are present\n */\n private validateRequiredParams(params: VerifyWebhookParams): void {\n if (!params.payload) {\n throw new CloseLoopError(\n \"Webhook payload is required\",\n ERROR_CODES.VALIDATION,\n 400\n )\n }\n\n if (!params.signature) {\n throw new CloseLoopError(\n \"Webhook signature is required\",\n ERROR_CODES.VALIDATION,\n 400\n )\n }\n\n if (!params.secret) {\n throw new CloseLoopError(\n \"Webhook secret is required\",\n ERROR_CODES.VALIDATION,\n 400\n )\n }\n }\n\n /**\n * Convert payload to string regardless of input type\n */\n private normalizePayload(payload: string | Buffer): string {\n return typeof payload === \"string\" ? payload : payload.toString(\"utf8\")\n }\n\n /**\n * Verify HMAC signature or throw error\n */\n private verifySignatureOrThrow(\n payload: string,\n signature: string,\n secret: string\n ): void {\n if (!verifySignature(payload, signature, secret)) {\n throw new CloseLoopError(\n \"Invalid webhook signature\",\n ERROR_CODES.INVALID_SIGNATURE,\n 401\n )\n }\n }\n\n /**\n * Parse JSON payload or throw error\n */\n private parseJsonOrThrow(payload: string): unknown {\n try {\n return JSON.parse(payload)\n } catch {\n throw new CloseLoopError(\n \"Invalid webhook payload: could not parse JSON\",\n ERROR_CODES.INVALID_PAYLOAD,\n 400\n )\n }\n }\n\n /**\n * Validate event structure using Zod schema\n */\n private validateEventStructure(parsed: unknown): WebhookEvent {\n const result = webhookEventSchema.safeParse(parsed)\n\n if (!result.success) {\n throw new CloseLoopError(\n \"Invalid webhook payload structure\",\n ERROR_CODES.INVALID_PAYLOAD,\n 400\n )\n }\n\n // Safe to cast: we've validated the base structure, and the type guards\n // will validate specific payload types when used\n return result.data as unknown as WebhookEvent\n }\n}\n","import { DEFAULT_TIMEOUT, USER_AGENT } from \"../constants\"\nimport {\n AuthenticationError,\n CloseLoopError,\n NetworkError,\n NotFoundError,\n RateLimitError\n} from \"./errors\"\n\nexport interface HttpClientOptions {\n baseUrl: string\n apiKey: string\n timeout?: number\n}\n\nexport interface RequestOptions {\n method: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\"\n path: string\n body?: unknown\n headers?: Record<string, string>\n}\n\ninterface ErrorResponse {\n message?: string\n code?: string\n details?: Record<string, unknown>\n}\n\n/**\n * HTTP client for making requests to the CloseLoop API\n */\nexport class HttpClient {\n private baseUrl: string\n private apiKey: string\n private timeout: number\n\n constructor(options: HttpClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/$/, \"\") // Remove trailing slash\n this.apiKey = options.apiKey\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT\n }\n\n /**\n * Make an HTTP request to the CloseLoop API\n */\n async request<T>(options: RequestOptions): Promise<T> {\n const url = `${this.baseUrl}${options.path}`\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), this.timeout)\n\n try {\n const response = await fetch(url, {\n method: options.method,\n headers: {\n \"Content-Type\": \"application/json\",\n \"User-Agent\": USER_AGENT,\n Authorization: `Bearer ${this.apiKey}`,\n ...options.headers\n },\n body: options.body ? JSON.stringify(options.body) : undefined,\n signal: controller.signal\n })\n\n clearTimeout(timeoutId)\n\n if (!response.ok) {\n await this.handleErrorResponse(response)\n }\n\n // Handle empty responses (204 No Content)\n if (response.status === 204) {\n return {} as T\n }\n\n return (await response.json()) as T\n } catch (error) {\n clearTimeout(timeoutId)\n\n if (error instanceof CloseLoopError) {\n throw error\n }\n\n if (error instanceof Error && error.name === \"AbortError\") {\n throw new NetworkError(\"Request timeout\")\n }\n\n throw new NetworkError(\n error instanceof Error ? error.message : \"Network request failed\",\n error instanceof Error ? error : undefined\n )\n }\n }\n\n private async handleErrorResponse(response: Response): Promise<never> {\n let errorData: ErrorResponse = {}\n\n try {\n const json = (await response.json()) as ErrorResponse\n errorData = json\n } catch {\n // Response body is not JSON\n }\n\n const message =\n errorData.message || `Request failed with status ${response.status}`\n\n switch (response.status) {\n case 401:\n throw new AuthenticationError(message)\n case 404:\n throw new NotFoundError(message)\n case 429: {\n const retryAfter = response.headers.get(\"Retry-After\")\n throw new RateLimitError(\n retryAfter ? parseInt(retryAfter, 10) : undefined\n )\n }\n default:\n throw new CloseLoopError(\n message,\n errorData.code || \"API_ERROR\",\n response.status,\n errorData.details\n )\n }\n }\n}\n","import { DEFAULT_BASE_URL } from \"./constants\"\nimport { Balances } from \"./resources/balances\"\nimport { Credits } from \"./resources/credits\"\nimport { Webhooks } from \"./resources/webhooks\"\nimport { HttpClient } from \"./utils/http\"\n\n/**\n * Configuration options for the CloseLoop client\n */\nexport interface CloseLoopOptions {\n /**\n * Your CloseLoop API key.\n * Get this from your CloseLoop dashboard.\n */\n apiKey: string\n\n /**\n * Base URL for the CloseLoop API.\n * Defaults to https://closeloop.app\n */\n baseUrl?: string\n\n /**\n * Request timeout in milliseconds.\n * Defaults to 30000 (30 seconds)\n */\n timeout?: number\n}\n\n/**\n * CloseLoop SDK client for credit billing integration.\n *\n * @example\n * ```typescript\n * import { CloseLoop } from \"@closeloop/sdk\"\n *\n * const client = new CloseLoop({\n * apiKey: process.env.CLOSELOOP_API_KEY!\n * })\n *\n * // Verify credits\n * const verification = await client.credits.verify({\n * walletAddress: \"0x1234...\",\n * planId: \"plan_abc123\",\n * amount: 1\n * })\n *\n * if (verification.hasEnoughCredits) {\n * // Process request...\n *\n * // Consume credit\n * await client.credits.consume({\n * walletAddress: \"0x1234...\",\n * planId: \"plan_abc123\",\n * amount: 1,\n * consumedBy: \"my-service\"\n * })\n * }\n * ```\n */\nexport class CloseLoop {\n private httpClient: HttpClient\n\n /**\n * Credit consumption and verification operations\n */\n readonly credits: Credits\n\n /**\n * Credit balance queries\n */\n readonly balances: Balances\n\n /**\n * Webhook signature verification utilities\n */\n readonly webhooks: Webhooks\n\n constructor(options: CloseLoopOptions) {\n if (!options.apiKey) {\n throw new Error(\"CloseLoop API key is required\")\n }\n\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL\n\n this.httpClient = new HttpClient({\n baseUrl,\n apiKey: options.apiKey,\n timeout: options.timeout\n })\n\n this.credits = new Credits(this.httpClient)\n this.balances = new Balances(this.httpClient)\n this.webhooks = new Webhooks()\n }\n}\n"],"mappings":"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","names":["ENDPOINTS"]}
1
+ {"version":3,"sources":["../src/constants.ts","../src/schemas/validation.ts","../src/utils/errors.ts","../src/utils/query.ts","../src/utils/validation.ts","../src/resources/balances.ts","../src/resources/credits.ts","../src/utils/crypto.ts","../src/resources/webhooks.ts","../src/utils/http.ts","../src/client.ts"],"sourcesContent":["// Declare the build-time injected version\ndeclare const __SDK_VERSION__: string\n\nexport const DEFAULT_BASE_URL = \"https://closeloop.app\"\nexport const DEFAULT_TIMEOUT = 30000 // 30 seconds\n\n// Version is injected at build time from package.json via tsup define\nexport const SDK_VERSION =\n typeof __SDK_VERSION__ !== \"undefined\" ? __SDK_VERSION__ : \"0.0.0\"\nexport const USER_AGENT = `closeloop-node/${SDK_VERSION}`\n\n// Webhook security settings\nexport const WEBHOOK_TIMESTAMP_TOLERANCE_SECONDS = 300 // 5 minutes\n","import { z } from \"zod\"\n\n/**\n * Ethereum wallet address validation (0x + 40 hex chars)\n */\nexport const walletAddressSchema = z\n .string()\n .regex(/^0x[a-fA-F0-9]{40}$/, \"Invalid Ethereum wallet address\")\n\n/**\n * Product ID validation (CUID or string)\n */\nexport const productIdSchema = z\n .string()\n .min(1, \"Product ID is required\")\n .max(100, \"Product ID too long\")\n\n/**\n * Plan ID validation (non-empty string with reasonable length)\n */\nexport const planIdSchema = z\n .string()\n .min(1, \"Plan ID is required\")\n .max(100, \"Plan ID too long\")\n\n/**\n * Balance ID validation\n */\nexport const balanceIdSchema = z\n .string()\n .min(1, \"Balance ID is required\")\n .max(100, \"Balance ID too long\")\n\n/**\n * Credit amount validation (positive integer with reasonable max)\n */\nexport const creditAmountSchema = z\n .number()\n .int(\"Amount must be an integer\")\n .positive(\"Amount must be positive\")\n .max(1_000_000_000, \"Amount exceeds maximum\")\n\n/**\n * Pagination limit validation\n */\nexport const paginationLimitSchema = z\n .number()\n .int()\n .positive()\n .max(100, \"Limit cannot exceed 100\")\n\n/**\n * Transaction type validation\n */\nexport const transactionTypeSchema = z.enum([\n \"PURCHASE\",\n \"CONSUMPTION\",\n \"REFUND\",\n \"EXPIRATION\"\n])\n\n/**\n * Dangerous prototype pollution keys to skip\n */\nconst DANGEROUS_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"])\n\n/**\n * Maximum depth for nested metadata sanitization\n * Prevents excessive recursion and DoS attacks\n */\nconst MAX_METADATA_DEPTH = 5\n\n/**\n * Sanitize metadata object to prevent prototype pollution.\n * Creates a clean object with no prototype chain.\n *\n * Security features:\n * - Recursively sanitizes nested objects\n * - Blocks __proto__, constructor, and prototype keys at all levels\n * - Limits recursion depth to prevent DoS\n * - Returns frozen objects to prevent mutation\n *\n * @param metadata - The metadata object to sanitize\n * @param depth - Current recursion depth (internal use)\n * @returns Sanitized and frozen metadata, or undefined if invalid\n */\nexport function sanitizeMetadata(\n metadata: Record<string, unknown> | undefined,\n depth = 0\n): Record<string, unknown> | undefined {\n // Reject if max depth exceeded (prevents DoS via deep nesting)\n if (depth > MAX_METADATA_DEPTH) return undefined\n if (!metadata) return undefined\n\n // Create a clean object with null prototype to prevent prototype pollution\n const clean = Object.create(null) as Record<string, unknown>\n\n for (const [key, value] of Object.entries(metadata)) {\n // Skip dangerous prototype pollution keys\n if (DANGEROUS_KEYS.has(key)) continue\n\n // Recursively sanitize nested objects\n if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n const sanitizedNested = sanitizeMetadata(\n value as Record<string, unknown>,\n depth + 1\n )\n // Only include if sanitization succeeded\n if (sanitizedNested !== undefined) {\n clean[key] = sanitizedNested\n }\n } else {\n // Copy primitives and arrays directly\n clean[key] = value\n }\n }\n\n return Object.freeze(clean)\n}\n\n// ============================================================================\n// Credit Schemas\n// ============================================================================\n\nexport const verifyCreditsSchema = z.object({\n walletAddress: walletAddressSchema,\n amount: creditAmountSchema,\n productId: productIdSchema\n})\n\nexport const consumeCreditsSchema = z.object({\n productId: productIdSchema,\n walletAddress: walletAddressSchema,\n amount: creditAmountSchema,\n consumedBy: z.string().max(100).optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n idempotencyKey: z.string().max(100).optional()\n})\n\n// ============================================================================\n// Balance Schemas\n// ============================================================================\n\nexport const getBalanceSchema = z.object({\n productId: productIdSchema,\n walletAddress: walletAddressSchema\n})\n\nexport const listBalancesSchema = z.object({\n walletAddress: walletAddressSchema,\n activeOnly: z.boolean().optional(),\n limit: paginationLimitSchema.optional(),\n cursor: z.string().max(200).optional()\n})\n\nexport const listTransactionsSchema = z.object({\n balanceId: balanceIdSchema,\n type: transactionTypeSchema.optional(),\n limit: paginationLimitSchema.optional(),\n cursor: z.string().max(200).optional()\n})\n\nexport const getStatsSchema = z.object({\n productId: productIdSchema,\n walletAddress: walletAddressSchema\n})\n\n// ============================================================================\n// Webhook Schemas\n// ============================================================================\n\nexport const webhookEventTypeSchema = z.enum([\n \"payment.success\",\n \"credits.low\",\n \"credits.expired\"\n])\n\nexport const webhookEventSchema = z.object({\n type: webhookEventTypeSchema,\n id: z.string().min(1),\n createdAt: z.string(),\n data: z.record(z.string(), z.unknown())\n})\n\nexport const paymentSuccessPayloadSchema = z\n .object({\n type: z.string(),\n productId: z.string(),\n planId: z.string(),\n transactionId: z.string(),\n walletAddress: z.string()\n })\n .loose()\n\nexport const creditsLowPayloadSchema = z.object({\n productId: z.string(),\n walletAddress: z.string(),\n remainingCredits: z.number(),\n threshold: z.number()\n})\n\nexport const creditsExpiredPayloadSchema = z.object({\n productId: z.string(),\n walletAddress: z.string(),\n expiredCredits: z.number(),\n expiresAt: z.string()\n})\n","/**\n * Base error class for all CloseLoop SDK errors\n */\nexport class CloseLoopError extends Error {\n constructor(\n message: string,\n public code: string,\n public statusCode?: number,\n public details?: Record<string, unknown>\n ) {\n super(message)\n this.name = \"CloseLoopError\"\n }\n}\n\n/**\n * Thrown when a user doesn't have enough credits for an operation\n */\nexport class InsufficientCreditsError extends CloseLoopError {\n constructor(\n public remainingCredits: number,\n public requiredCredits: number\n ) {\n super(\n `Insufficient credits: ${remainingCredits} available, ${requiredCredits} required`,\n \"INSUFFICIENT_CREDITS\",\n 402\n )\n this.name = \"InsufficientCreditsError\"\n }\n}\n\n/**\n * Thrown when credits have expired\n */\nexport class CreditsExpiredError extends CloseLoopError {\n constructor(public expiresAt: Date) {\n super(\n `Credits expired at ${expiresAt.toISOString()}`,\n \"CREDITS_EXPIRED\",\n 410\n )\n this.name = \"CreditsExpiredError\"\n }\n}\n\n/**\n * Thrown when authentication fails (invalid API key)\n */\nexport class AuthenticationError extends CloseLoopError {\n constructor(message = \"Invalid API key\") {\n super(message, \"AUTHENTICATION_ERROR\", 401)\n this.name = \"AuthenticationError\"\n }\n}\n\n/**\n * Thrown when rate limit is exceeded\n */\nexport class RateLimitError extends CloseLoopError {\n constructor(public retryAfter?: number) {\n super(\"Rate limit exceeded\", \"RATE_LIMIT\", 429)\n this.name = \"RateLimitError\"\n }\n}\n\n/**\n * Thrown when a network error occurs\n */\nexport class NetworkError extends CloseLoopError {\n constructor(\n message: string,\n public cause?: Error\n ) {\n super(message, \"NETWORK_ERROR\")\n this.name = \"NetworkError\"\n }\n}\n\n/**\n * Thrown when a resource is not found\n */\nexport class NotFoundError extends CloseLoopError {\n constructor(resource: string) {\n super(`${resource} not found`, \"NOT_FOUND\", 404)\n this.name = \"NotFoundError\"\n }\n}\n\n/**\n * Thrown when input validation fails\n */\nexport class ValidationError extends CloseLoopError {\n constructor(\n message: string,\n public field?: string\n ) {\n super(message, \"VALIDATION_ERROR\", 400)\n this.name = \"ValidationError\"\n }\n}\n","/**\n * Build URL query string from params, filtering out undefined values.\n * Properly encodes values for safe URL transmission.\n *\n * @param params - Key-value pairs to convert to query string\n * @returns URL-encoded query string (without leading ?)\n *\n * @example\n * ```typescript\n * buildQueryString({ foo: 'bar', num: 42, empty: undefined })\n * // Returns: \"foo=bar&num=42\"\n * ```\n */\nexport function buildQueryString(\n params: Record<string, string | number | boolean | undefined>\n): string {\n const query = new URLSearchParams()\n\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) {\n query.set(key, String(value))\n }\n }\n\n return query.toString()\n}\n","import { CloseLoopError } from \"./errors\"\n\n/**\n * Generic schema interface for validation\n */\nexport interface ValidationSchema<T> {\n safeParse: (data: unknown) => {\n success: boolean\n data?: T\n error?: { message: string }\n }\n}\n\n/**\n * Validate input against a Zod schema\n * Throws CloseLoopError if validation fails\n *\n * @param schema - Zod schema to validate against\n * @param data - Data to validate\n * @returns Validated and typed data\n * @throws {CloseLoopError} When validation fails\n */\nexport function validateInput<T>(\n schema: ValidationSchema<T>,\n data: unknown\n): T {\n const result = schema.safeParse(data)\n\n if (!result.success) {\n throw new CloseLoopError(\n `Validation error: ${result.error?.message || \"Invalid input\"}`,\n \"VALIDATION_ERROR\",\n 400\n )\n }\n\n return result.data as T\n}\n\n/**\n * Validate a single value against a Zod schema\n * Returns a boolean without throwing\n */\nexport function isValid<T>(\n schema: ValidationSchema<T>,\n data: unknown\n): data is T {\n return schema.safeParse(data).success\n}\n","import {\n getBalanceSchema,\n getStatsSchema,\n listBalancesSchema,\n listTransactionsSchema\n} from \"../schemas/validation\"\nimport type {\n CreditBalance,\n CreditStats,\n GetBalanceParams,\n ListBalancesParams,\n ListBalancesResponse,\n ListTransactionsParams,\n ListTransactionsResponse\n} from \"../types/balances\"\nimport { NotFoundError } from \"../utils/errors\"\nimport { HttpClient } from \"../utils/http\"\nimport { buildQueryString } from \"../utils/query\"\nimport { validateInput } from \"../utils/validation\"\n\n// API Endpoints\nconst ENDPOINTS = {\n BALANCE: \"/api/credit/balance\",\n BALANCES: \"/api/credit/balances\",\n STATS: \"/api/credit/stats\"\n}\n\n/**\n * Resource for querying credit balances and transaction history\n */\nexport class Balances {\n constructor(private readonly http: HttpClient) {}\n\n /**\n * Get a specific credit balance for a wallet and plan.\n *\n * @example\n * ```typescript\n * const balance = await client.balances.get({\n * walletAddress: \"0x1234...\"\n * })\n *\n * if (balance) {\n * console.log(`Credits: ${balance.remainingCredits}/${balance.totalCredits}`)\n * }\n * ```\n *\n * @returns The credit balance, or null if not found\n * @throws {CloseLoopError} When input validation fails\n */\n async get(params: GetBalanceParams): Promise<CreditBalance | null> {\n const validated = validateInput(getBalanceSchema, params)\n\n const query = buildQueryString({\n walletAddress: validated.walletAddress,\n productId: validated.productId\n })\n\n try {\n return await this.http.request<CreditBalance>({\n method: \"GET\",\n path: `${ENDPOINTS.BALANCE}?${query}`\n })\n } catch (error) {\n if (error instanceof NotFoundError) {\n return null\n }\n throw error\n }\n }\n\n /**\n * List all credit balances for a wallet.\n *\n * @example\n * ```typescript\n * const { balances, nextCursor } = await client.balances.list({\n * walletAddress: \"0x1234...\",\n * activeOnly: true,\n * limit: 10\n * })\n *\n * for (const balance of balances) {\n * console.log(`${balance.totalCredits}: ${balance.remainingCredits} credits`)\n * }\n *\n * // Paginate if needed\n * if (nextCursor) {\n * const nextPage = await client.balances.list({\n * walletAddress: \"0x1234...\",\n * cursor: nextCursor\n * })\n * }\n * ```\n *\n * @throws {CloseLoopError} When input validation fails\n */\n async list(params: ListBalancesParams): Promise<ListBalancesResponse> {\n const validated = validateInput(listBalancesSchema, params)\n\n const query = buildQueryString({\n walletAddress: validated.walletAddress,\n activeOnly: validated.activeOnly,\n limit: validated.limit,\n cursor: validated.cursor\n })\n\n return this.http.request<ListBalancesResponse>({\n method: \"GET\",\n path: `${ENDPOINTS.BALANCES}?${query}`\n })\n }\n\n /**\n * Get transaction history for a balance.\n *\n * @example\n * ```typescript\n * const { transactions } = await client.balances.transactions({\n * balanceId: \"bal_xyz\",\n * type: \"CONSUMPTION\",\n * limit: 50\n * })\n *\n * for (const tx of transactions) {\n * console.log(`${tx.type}: ${tx.amount} - ${tx.description}`)\n * }\n * ```\n *\n * @throws {CloseLoopError} When input validation fails\n */\n async transactions(\n params: ListTransactionsParams\n ): Promise<ListTransactionsResponse> {\n const validated = validateInput(listTransactionsSchema, params)\n\n const query = buildQueryString({\n type: validated.type,\n limit: validated.limit,\n cursor: validated.cursor\n })\n\n const basePath = `${ENDPOINTS.BALANCE}/${encodeURIComponent(validated.balanceId)}/transactions`\n const path = query ? `${basePath}?${query}` : basePath\n\n return this.http.request<ListTransactionsResponse>({\n method: \"GET\",\n path\n })\n }\n\n /**\n * Get aggregated stats for a wallet's credits.\n *\n * @example\n * ```typescript\n * const stats = await client.balances.stats({\n * walletAddress: \"0x1234...\",\n * productId: \"prod_123\"\n * })\n * console.log(`Total: ${stats.totalCredits}, Used: ${stats.totalUsed}`)\n * ```\n *\n * @throws {CloseLoopError} When input validation fails\n */\n async stats(params: {\n walletAddress: string\n productId: string\n }): Promise<CreditStats> {\n // Validate using the shared utility for consistency\n validateInput(getStatsSchema, params)\n\n const query = buildQueryString({\n walletAddress: params.walletAddress,\n productId: params.productId\n })\n\n return this.http.request<CreditStats>({\n method: \"GET\",\n path: `${ENDPOINTS.STATS}?${query}`\n })\n }\n}\n","import {\n consumeCreditsSchema,\n sanitizeMetadata,\n verifyCreditsSchema\n} from \"../schemas/validation\"\nimport type {\n ConsumeCreditsParams,\n ConsumeCreditsResponse,\n VerifyCreditsParams,\n VerifyCreditsResponse\n} from \"../types/credits\"\nimport { CreditsExpiredError, InsufficientCreditsError } from \"../utils/errors\"\nimport { HttpClient } from \"../utils/http\"\nimport { validateInput } from \"../utils/validation\"\n\n// API Endpoints\nconst ENDPOINTS = {\n VERIFY: \"/api/credit/verify\",\n CONSUME: \"/api/credit/consume\"\n}\n\n/**\n * Resource for credit verification and consumption operations\n */\nexport class Credits {\n constructor(private readonly http: HttpClient) {}\n\n /**\n * Verify if a user has enough credits without consuming them.\n *\n * @example\n * ```typescript\n * const result = await client.credits.verify({\n * walletAddress: \"0x1234...\",\n * amount: 10\n * })\n *\n * if (result.hasEnough) {\n * // Proceed with service\n * }\n * ```\n *\n * @throws {CloseLoopError} When input validation fails\n */\n async verify(params: VerifyCreditsParams): Promise<VerifyCreditsResponse> {\n const validated = validateInput(verifyCreditsSchema, params)\n\n return this.http.request<VerifyCreditsResponse>({\n method: \"POST\",\n path: ENDPOINTS.VERIFY,\n body: validated\n })\n }\n\n /**\n * Consume credits from a user's balance.\n *\n * @example\n * ```typescript\n * const result = await client.credits.consume({\n * walletAddress: \"0x1234...\",\n * amount: 1,\n * consumedBy: \"ai-generation\",\n * metadata: { requestId: \"req_xyz\" }\n * })\n *\n * console.log(`Remaining: ${result.remainingCredits}`)\n * ```\n *\n * @throws {InsufficientCreditsError} When user doesn't have enough credits\n * @throws {CreditsExpiredError} When credits have expired\n * @throws {CloseLoopError} When input validation fails\n */\n async consume(params: ConsumeCreditsParams): Promise<ConsumeCreditsResponse> {\n const validated = validateInput(consumeCreditsSchema, params)\n\n return this.http.request<ConsumeCreditsResponse>({\n method: \"POST\",\n path: ENDPOINTS.CONSUME,\n body: this.buildConsumeBody(validated),\n headers: this.buildIdempotencyHeaders(validated.idempotencyKey)\n })\n }\n\n /**\n * Verify and consume credits in a two-step operation.\n *\n * **Important**: This method performs TWO separate API calls (verify, then consume).\n * There is a potential race condition between the verify and consume steps where\n * another request could consume credits. For critical operations, consider:\n * - Using an `idempotencyKey` to prevent duplicate consumption\n * - Implementing server-side locking if your use case requires strict atomicity\n *\n * @example\n * ```typescript\n * try {\n * const result = await client.credits.verifyAndConsume({\n * walletAddress: \"0x1234...\",\n * amount: 5,\n * consumedBy: \"batch-processing\",\n * idempotencyKey: \"unique-request-id\" // Recommended for safety\n * })\n * // Credits were verified and consumed\n * } catch (error) {\n * if (error instanceof InsufficientCreditsError) {\n * // Handle insufficient credits\n * }\n * }\n * ```\n *\n * @throws {InsufficientCreditsError} When user doesn't have enough credits\n * @throws {CreditsExpiredError} When credits have expired\n * @throws {CloseLoopError} When input validation fails\n */\n async verifyAndConsume(\n params: ConsumeCreditsParams\n ): Promise<ConsumeCreditsResponse> {\n const validated = validateInput(consumeCreditsSchema, params)\n\n // First verify\n const verification = await this.http.request<VerifyCreditsResponse>({\n method: \"POST\",\n path: ENDPOINTS.VERIFY,\n body: {\n walletAddress: validated.walletAddress,\n amount: validated.amount,\n productId: validated.productId\n }\n })\n\n this.assertCreditsAvailable(verification, validated.amount)\n\n // Then consume\n return this.http.request<ConsumeCreditsResponse>({\n method: \"POST\",\n path: ENDPOINTS.CONSUME,\n body: this.buildConsumeBody(validated),\n headers: this.buildIdempotencyHeaders(validated.idempotencyKey)\n })\n }\n\n // ==========================================================================\n // Private Helpers\n // ==========================================================================\n\n /**\n * Build consumption request body with sanitized metadata\n */\n private buildConsumeBody(validated: {\n walletAddress: string\n amount: number\n productId: string\n consumedBy?: string\n metadata?: Record<string, unknown>\n }) {\n return {\n walletAddress: validated.walletAddress,\n amount: validated.amount,\n productId: validated.productId,\n consumedBy: validated.consumedBy,\n metadata: sanitizeMetadata(validated.metadata)\n }\n }\n\n /**\n * Build idempotency headers if key is provided\n */\n private buildIdempotencyHeaders(\n idempotencyKey?: string\n ): Record<string, string> {\n if (!idempotencyKey) return {}\n return { \"Idempotency-Key\": idempotencyKey }\n }\n\n /**\n * Assert credits are available, throw typed errors if not\n */\n private assertCreditsAvailable(\n verification: VerifyCreditsResponse,\n requiredAmount: number\n ): void {\n if (!verification.hasEnoughCredits) {\n throw new InsufficientCreditsError(\n verification.remainingCredits,\n requiredAmount\n )\n }\n\n if (verification.expiresAt) {\n const expirationDate = new Date(verification.expiresAt)\n if (expirationDate < new Date()) {\n throw new CreditsExpiredError(expirationDate)\n }\n }\n }\n}\n","import { createHmac, timingSafeEqual } from \"crypto\"\n\n/**\n * Generates an HMAC signature for webhook verification\n */\nexport function generateSignature(payload: string, secret: string): string {\n return createHmac(\"sha256\", secret).update(payload).digest(\"hex\")\n}\n\n/**\n * Verifies that a webhook signature is valid using timing-safe comparison\n */\nexport function verifySignature(\n payload: string,\n signature: string,\n secret: string\n): boolean {\n const expectedSignature = generateSignature(payload, secret)\n\n // Use timing-safe comparison to prevent timing attacks\n if (signature.length !== expectedSignature.length) {\n return false\n }\n\n return timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))\n}\n","import { WEBHOOK_TIMESTAMP_TOLERANCE_SECONDS } from \"../constants\"\nimport {\n creditsExpiredPayloadSchema,\n creditsLowPayloadSchema,\n paymentSuccessPayloadSchema,\n webhookEventSchema\n} from \"../schemas/validation\"\nimport type {\n CreditsExpiredPayload,\n CreditsLowPayload,\n PaymentSuccessPayload,\n WebhookEvent,\n WebhookPayload\n} from \"../types/webhooks\"\nimport { verifySignature } from \"../utils/crypto\"\nimport { CloseLoopError } from \"../utils/errors\"\n\n/**\n * Parameters for verifying a webhook\n */\nexport interface VerifyWebhookParams {\n /**\n * Raw request body as string or Buffer\n */\n payload: string | Buffer\n\n /**\n * X-CloseLoop-Signature header value\n */\n signature: string\n\n /**\n * Your webhook secret\n */\n secret: string\n\n /**\n * Optional: Custom timestamp tolerance in seconds.\n * Webhooks older than this will be rejected to prevent replay attacks.\n * @default 300 (5 minutes)\n */\n toleranceSeconds?: number\n}\n\n// Validation error codes\nconst ERROR_CODES = {\n VALIDATION: \"VALIDATION_ERROR\",\n INVALID_SIGNATURE: \"INVALID_SIGNATURE\",\n INVALID_PAYLOAD: \"INVALID_PAYLOAD\",\n TIMESTAMP_EXPIRED: \"TIMESTAMP_EXPIRED\"\n}\n\n/**\n * Resource for webhook signature verification and event handling\n */\nexport class Webhooks {\n /**\n * Verify a webhook signature and parse the event.\n *\n * @example\n * ```typescript\n * // Express handler\n * app.post(\"/webhook\", (req, res) => {\n * try {\n * const event = client.webhooks.verify({\n * payload: req.body,\n * signature: req.headers[\"x-closeloop-signature\"],\n * secret: process.env.WEBHOOK_SECRET!\n * })\n *\n * if (client.webhooks.isPaymentSuccess(event)) {\n * const { type, walletAddress, planId, amount } = event.data\n * // Handle payment success\n * }\n *\n * res.json({ received: true })\n * } catch (error) {\n * res.status(400).json({ error: \"Invalid signature\" })\n * }\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Next.js App Router\n * export async function POST(request: Request) {\n * const payload = await request.text()\n * const signature = request.headers.get(\"x-closeloop-signature\")!\n *\n * const event = client.webhooks.verify({\n * payload,\n * signature,\n * secret: process.env.WEBHOOK_SECRET!\n * })\n *\n * // Handle event...\n * return Response.json({ received: true })\n * }\n * ```\n *\n * @throws {CloseLoopError} When signature is invalid or payload is malformed\n */\n verify(params: VerifyWebhookParams): WebhookEvent {\n this.validateRequiredParams(params)\n\n const payloadString = this.normalizePayload(params.payload)\n\n this.verifySignatureOrThrow(payloadString, params.signature, params.secret)\n\n const parsed = this.parseJsonOrThrow(payloadString)\n\n const event = this.validateEventStructure(parsed)\n\n // Validate timestamp to prevent replay attacks\n const tolerance =\n params.toleranceSeconds ?? WEBHOOK_TIMESTAMP_TOLERANCE_SECONDS\n this.validateTimestamp(event.createdAt, tolerance)\n\n return event\n }\n\n /**\n * Type guard for payment.success events\n * Also validates the payload structure\n *\n * @example\n * ```typescript\n * if (client.webhooks.isPaymentSuccess(event)) {\n * // TypeScript knows event.data is PaymentSuccessPayload\n * console.log(event.data.planId, event.data.walletAddress)\n * }\n * ```\n */\n isPaymentSuccess(\n event: WebhookEvent<WebhookPayload>\n ): event is WebhookEvent<PaymentSuccessPayload> {\n return (\n event.type === \"payment.success\" &&\n paymentSuccessPayloadSchema.safeParse(event.data).success\n )\n }\n\n /**\n * Type guard for credits.low events\n * Also validates the payload structure\n *\n * @example\n * ```typescript\n * if (client.webhooks.isCreditsLow(event)) {\n * // TypeScript knows event.data is CreditsLowPayload\n * console.log(`Low balance: ${event.data.remainingCredits}`)\n * }\n * ```\n */\n isCreditsLow(\n event: WebhookEvent<WebhookPayload>\n ): event is WebhookEvent<CreditsLowPayload> {\n return (\n event.type === \"credits.low\" &&\n creditsLowPayloadSchema.safeParse(event.data).success\n )\n }\n\n /**\n * Type guard for credits.expired events\n * Also validates the payload structure\n *\n * @example\n * ```typescript\n * if (client.webhooks.isCreditsExpired(event)) {\n * // TypeScript knows event.data is CreditsExpiredPayload\n * console.log(`Expired: ${event.data.expiredCredits} credits`)\n * }\n * ```\n */\n isCreditsExpired(\n event: WebhookEvent<WebhookPayload>\n ): event is WebhookEvent<CreditsExpiredPayload> {\n return (\n event.type === \"credits.expired\" &&\n creditsExpiredPayloadSchema.safeParse(event.data).success\n )\n }\n\n // ==========================================================================\n // Private Helpers\n // ==========================================================================\n\n /**\n * Validate that all required parameters are present\n */\n private validateRequiredParams(params: VerifyWebhookParams): void {\n if (!params.payload) {\n throw new CloseLoopError(\n \"Webhook payload is required\",\n ERROR_CODES.VALIDATION,\n 400\n )\n }\n\n if (!params.signature) {\n throw new CloseLoopError(\n \"Webhook signature is required\",\n ERROR_CODES.VALIDATION,\n 400\n )\n }\n\n if (!params.secret) {\n throw new CloseLoopError(\n \"Webhook secret is required\",\n ERROR_CODES.VALIDATION,\n 400\n )\n }\n }\n\n /**\n * Convert payload to string regardless of input type\n */\n private normalizePayload(payload: string | Buffer): string {\n return typeof payload === \"string\" ? payload : payload.toString(\"utf8\")\n }\n\n /**\n * Verify HMAC signature or throw error\n */\n private verifySignatureOrThrow(\n payload: string,\n signature: string,\n secret: string\n ): void {\n if (!verifySignature(payload, signature, secret)) {\n throw new CloseLoopError(\n \"Invalid webhook signature\",\n ERROR_CODES.INVALID_SIGNATURE,\n 401\n )\n }\n }\n\n /**\n * Parse JSON payload or throw error\n */\n private parseJsonOrThrow(payload: string): unknown {\n try {\n return JSON.parse(payload)\n } catch {\n throw new CloseLoopError(\n \"Invalid webhook payload: could not parse JSON\",\n ERROR_CODES.INVALID_PAYLOAD,\n 400\n )\n }\n }\n\n /**\n * Validate event structure using Zod schema\n */\n private validateEventStructure(parsed: unknown): WebhookEvent {\n const result = webhookEventSchema.safeParse(parsed)\n\n if (!result.success) {\n throw new CloseLoopError(\n \"Invalid webhook payload structure\",\n ERROR_CODES.INVALID_PAYLOAD,\n 400\n )\n }\n\n // Construct the validated event with proper typing\n // The type guards (isPaymentSuccess, etc.) will validate specific payload types\n const validatedData = result.data\n return {\n id: validatedData.id,\n type: validatedData.type as WebhookEvent[\"type\"],\n createdAt: validatedData.createdAt,\n data: validatedData.data as WebhookEvent[\"data\"]\n }\n }\n\n /**\n * Validate webhook timestamp to prevent replay attacks\n * Rejects webhooks older than the specified tolerance\n */\n private validateTimestamp(createdAt: string, toleranceSeconds: number): void {\n const eventTime = new Date(createdAt).getTime()\n const now = Date.now()\n const ageSeconds = (now - eventTime) / 1000\n\n // Allow for some clock skew (webhook could be slightly in the future)\n if (ageSeconds > toleranceSeconds || ageSeconds < -60) {\n throw new CloseLoopError(\n `Webhook timestamp expired or invalid. Event age: ${Math.round(ageSeconds)}s, tolerance: ${toleranceSeconds}s`,\n ERROR_CODES.TIMESTAMP_EXPIRED,\n 400\n )\n }\n }\n}\n","import { DEFAULT_TIMEOUT, USER_AGENT } from \"../constants\"\nimport {\n AuthenticationError,\n CloseLoopError,\n NetworkError,\n NotFoundError,\n RateLimitError\n} from \"./errors\"\n\nexport interface HttpClientOptions {\n baseUrl: string\n apiKey: string\n timeout?: number\n}\n\nexport interface RequestOptions {\n method: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\"\n path: string\n body?: unknown\n headers?: Record<string, string>\n}\n\ninterface ErrorResponse {\n message?: string\n code?: string\n details?: Record<string, unknown>\n}\n\n/**\n * HTTP client for making requests to the CloseLoop API\n */\nexport class HttpClient {\n private baseUrl: string\n private apiKey: string\n private timeout: number\n\n constructor(options: HttpClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/$/, \"\") // Remove trailing slash\n this.apiKey = options.apiKey\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT\n }\n\n /**\n * Make an HTTP request to the CloseLoop API\n */\n async request<T>(options: RequestOptions): Promise<T> {\n const url = `${this.baseUrl}${options.path}`\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), this.timeout)\n\n try {\n const response = await fetch(url, {\n method: options.method,\n headers: {\n \"Content-Type\": \"application/json\",\n \"User-Agent\": USER_AGENT,\n Authorization: `Bearer ${this.apiKey}`,\n ...options.headers\n },\n body: options.body ? JSON.stringify(options.body) : undefined,\n signal: controller.signal\n })\n\n clearTimeout(timeoutId)\n\n if (!response.ok) {\n await this.handleErrorResponse(response)\n }\n\n // Handle empty responses (204 No Content)\n if (response.status === 204) {\n return {} as T\n }\n\n return (await response.json()) as T\n } catch (error) {\n clearTimeout(timeoutId)\n\n if (error instanceof CloseLoopError) {\n throw error\n }\n\n if (error instanceof Error && error.name === \"AbortError\") {\n throw new NetworkError(\"Request timeout\")\n }\n\n throw new NetworkError(\n error instanceof Error ? error.message : \"Network request failed\",\n error instanceof Error ? error : undefined\n )\n }\n }\n\n private async handleErrorResponse(response: Response): Promise<never> {\n let errorData: ErrorResponse = {}\n\n try {\n const json = (await response.json()) as ErrorResponse\n errorData = json\n } catch {\n // Response body is not JSON\n }\n\n const message =\n errorData.message || `Request failed with status ${response.status}`\n\n switch (response.status) {\n case 401:\n throw new AuthenticationError(message)\n case 404:\n throw new NotFoundError(message)\n case 429: {\n const retryAfter = response.headers.get(\"Retry-After\")\n throw new RateLimitError(\n retryAfter ? parseInt(retryAfter, 10) : undefined\n )\n }\n default:\n throw new CloseLoopError(\n message,\n errorData.code || \"API_ERROR\",\n response.status,\n errorData.details\n )\n }\n }\n}\n","import { DEFAULT_BASE_URL } from \"./constants\"\nimport { Balances } from \"./resources/balances\"\nimport { Credits } from \"./resources/credits\"\nimport { Webhooks } from \"./resources/webhooks\"\nimport { AuthenticationError } from \"./utils/errors\"\nimport { HttpClient } from \"./utils/http\"\n\n/**\n * Configuration options for the CloseLoop client\n */\nexport interface CloseLoopOptions {\n /**\n * Your CloseLoop API key.\n * Get this from your CloseLoop dashboard.\n */\n apiKey: string\n\n /**\n * Base URL for the CloseLoop API.\n * Defaults to https://closeloop.app\n */\n baseUrl?: string\n\n /**\n * Request timeout in milliseconds.\n * Defaults to 30000 (30 seconds)\n */\n timeout?: number\n}\n\n/**\n * CloseLoop SDK client for credit billing integration.\n *\n * @example\n * ```typescript\n * import { CloseLoop } from \"@closeloop/sdk\"\n *\n * const client = new CloseLoop({\n * apiKey: process.env.CLOSELOOP_API_KEY!\n * })\n *\n * // Verify credits\n * const verification = await client.credits.verify({\n * walletAddress: \"0x1234...\",\n * planId: \"plan_abc123\",\n * amount: 1\n * })\n *\n * if (verification.hasEnoughCredits) {\n * // Process request...\n *\n * // Consume credit\n * await client.credits.consume({\n * walletAddress: \"0x1234...\",\n * planId: \"plan_abc123\",\n * amount: 1,\n * consumedBy: \"my-service\"\n * })\n * }\n * ```\n */\nexport class CloseLoop {\n private httpClient: HttpClient\n\n /**\n * Credit consumption and verification operations\n */\n readonly credits: Credits\n\n /**\n * Credit balance queries\n */\n readonly balances: Balances\n\n /**\n * Webhook signature verification utilities\n */\n readonly webhooks: Webhooks\n\n constructor(options: CloseLoopOptions) {\n // Validate API key: must be a non-empty string with reasonable minimum length\n if (\n !options.apiKey ||\n typeof options.apiKey !== \"string\" ||\n options.apiKey.trim().length < 10\n ) {\n throw new AuthenticationError(\n \"CloseLoop API key is required and must be at least 10 characters\"\n )\n }\n\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL\n\n this.httpClient = new HttpClient({\n baseUrl,\n apiKey: options.apiKey,\n timeout: options.timeout\n })\n\n this.credits = new Credits(this.httpClient)\n this.balances = new Balances(this.httpClient)\n this.webhooks = new Webhooks()\n }\n}\n"],"mappings":"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","names":["ENDPOINTS"]}