@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.js CHANGED
@@ -39,12 +39,14 @@ module.exports = __toCommonJS(src_exports);
39
39
  // src/constants.ts
40
40
  var DEFAULT_BASE_URL = "https://closeloop.app";
41
41
  var DEFAULT_TIMEOUT = 3e4;
42
- var SDK_VERSION = "0.1.0";
42
+ var SDK_VERSION = true ? "0.1.9" : "0.0.0";
43
43
  var USER_AGENT = `closeloop-node/${SDK_VERSION}`;
44
+ var WEBHOOK_TIMESTAMP_TOLERANCE_SECONDS = 300;
44
45
 
45
46
  // src/schemas/validation.ts
46
47
  var import_zod = require("zod");
47
48
  var walletAddressSchema = import_zod.z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum wallet address");
49
+ var productIdSchema = import_zod.z.string().min(1, "Product ID is required").max(100, "Product ID too long");
48
50
  var planIdSchema = import_zod.z.string().min(1, "Plan ID is required").max(100, "Plan ID too long");
49
51
  var balanceIdSchema = import_zod.z.string().min(1, "Balance ID is required").max(100, "Balance ID too long");
50
52
  var creditAmountSchema = import_zod.z.number().int("Amount must be an integer").positive("Amount must be positive").max(1e9, "Amount exceeds maximum");
@@ -56,43 +58,43 @@ var transactionTypeSchema = import_zod.z.enum([
56
58
  "EXPIRATION"
57
59
  ]);
58
60
  var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
59
- function sanitizeMetadata(metadata) {
61
+ var MAX_METADATA_DEPTH = 5;
62
+ function sanitizeMetadata(metadata, depth = 0) {
63
+ if (depth > MAX_METADATA_DEPTH) return void 0;
60
64
  if (!metadata) return void 0;
61
65
  const clean = /* @__PURE__ */ Object.create(null);
62
66
  for (const [key, value] of Object.entries(metadata)) {
63
67
  if (DANGEROUS_KEYS.has(key)) continue;
64
- clean[key] = value;
68
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
69
+ const sanitizedNested = sanitizeMetadata(
70
+ value,
71
+ depth + 1
72
+ );
73
+ if (sanitizedNested !== void 0) {
74
+ clean[key] = sanitizedNested;
75
+ }
76
+ } else {
77
+ clean[key] = value;
78
+ }
65
79
  }
66
80
  return Object.freeze(clean);
67
81
  }
68
82
  var verifyCreditsSchema = import_zod.z.object({
69
83
  walletAddress: walletAddressSchema,
70
- planId: planIdSchema,
71
- amount: creditAmountSchema
84
+ amount: creditAmountSchema,
85
+ productId: productIdSchema
72
86
  });
73
87
  var consumeCreditsSchema = import_zod.z.object({
88
+ productId: productIdSchema,
74
89
  walletAddress: walletAddressSchema,
75
- planId: planIdSchema,
76
90
  amount: creditAmountSchema,
77
91
  consumedBy: import_zod.z.string().max(100).optional(),
78
92
  metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
79
93
  idempotencyKey: import_zod.z.string().max(100).optional()
80
94
  });
81
- var batchConsumeSchema = import_zod.z.object({
82
- operations: import_zod.z.array(
83
- import_zod.z.object({
84
- walletAddress: walletAddressSchema,
85
- planId: planIdSchema,
86
- amount: creditAmountSchema,
87
- consumedBy: import_zod.z.string().max(100).optional(),
88
- metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
89
- })
90
- ).min(1, "At least one operation required").max(100, "Maximum 100 operations per batch"),
91
- atomic: import_zod.z.boolean().optional()
92
- });
93
95
  var getBalanceSchema = import_zod.z.object({
94
- walletAddress: walletAddressSchema,
95
- planId: planIdSchema
96
+ productId: productIdSchema,
97
+ walletAddress: walletAddressSchema
96
98
  });
97
99
  var listBalancesSchema = import_zod.z.object({
98
100
  walletAddress: walletAddressSchema,
@@ -106,6 +108,10 @@ var listTransactionsSchema = import_zod.z.object({
106
108
  limit: paginationLimitSchema.optional(),
107
109
  cursor: import_zod.z.string().max(200).optional()
108
110
  });
111
+ var getStatsSchema = import_zod.z.object({
112
+ productId: productIdSchema,
113
+ walletAddress: walletAddressSchema
114
+ });
109
115
  var webhookEventTypeSchema = import_zod.z.enum([
110
116
  "payment.success",
111
117
  "credits.low",
@@ -119,21 +125,20 @@ var webhookEventSchema = import_zod.z.object({
119
125
  });
120
126
  var paymentSuccessPayloadSchema = import_zod.z.object({
121
127
  type: import_zod.z.string(),
128
+ productId: import_zod.z.string(),
122
129
  planId: import_zod.z.string(),
123
- planName: import_zod.z.string(),
124
- price: import_zod.z.number(),
125
- walletAddress: import_zod.z.string(),
126
- transactionId: import_zod.z.string()
130
+ transactionId: import_zod.z.string(),
131
+ walletAddress: import_zod.z.string()
127
132
  }).loose();
128
133
  var creditsLowPayloadSchema = import_zod.z.object({
134
+ productId: import_zod.z.string(),
129
135
  walletAddress: import_zod.z.string(),
130
- planId: import_zod.z.string(),
131
136
  remainingCredits: import_zod.z.number(),
132
137
  threshold: import_zod.z.number()
133
138
  });
134
139
  var creditsExpiredPayloadSchema = import_zod.z.object({
140
+ productId: import_zod.z.string(),
135
141
  walletAddress: import_zod.z.string(),
136
- planId: import_zod.z.string(),
137
142
  expiredCredits: import_zod.z.number(),
138
143
  expiresAt: import_zod.z.string()
139
144
  });
@@ -205,6 +210,17 @@ var ValidationError = class extends CloseLoopError {
205
210
  }
206
211
  };
207
212
 
213
+ // src/utils/query.ts
214
+ function buildQueryString(params) {
215
+ const query = new URLSearchParams();
216
+ for (const [key, value] of Object.entries(params)) {
217
+ if (value !== void 0) {
218
+ query.set(key, String(value));
219
+ }
220
+ }
221
+ return query.toString();
222
+ }
223
+
208
224
  // src/utils/validation.ts
209
225
  function validateInput(schema, data) {
210
226
  const result = schema.safeParse(data);
@@ -234,8 +250,7 @@ var Balances = class {
234
250
  * @example
235
251
  * ```typescript
236
252
  * const balance = await client.balances.get({
237
- * walletAddress: "0x1234...",
238
- * planId: "plan_abc123"
253
+ * walletAddress: "0x1234..."
239
254
  * })
240
255
  *
241
256
  * if (balance) {
@@ -248,9 +263,9 @@ var Balances = class {
248
263
  */
249
264
  async get(params) {
250
265
  const validated = validateInput(getBalanceSchema, params);
251
- const query = this.buildQueryString({
266
+ const query = buildQueryString({
252
267
  walletAddress: validated.walletAddress,
253
- planId: validated.planId
268
+ productId: validated.productId
254
269
  });
255
270
  try {
256
271
  return await this.http.request({
@@ -276,7 +291,7 @@ var Balances = class {
276
291
  * })
277
292
  *
278
293
  * for (const balance of balances) {
279
- * console.log(`${balance.planName}: ${balance.remainingCredits} credits`)
294
+ * console.log(`${balance.totalCredits}: ${balance.remainingCredits} credits`)
280
295
  * }
281
296
  *
282
297
  * // Paginate if needed
@@ -292,7 +307,7 @@ var Balances = class {
292
307
  */
293
308
  async list(params) {
294
309
  const validated = validateInput(listBalancesSchema, params);
295
- const query = this.buildQueryString({
310
+ const query = buildQueryString({
296
311
  walletAddress: validated.walletAddress,
297
312
  activeOnly: validated.activeOnly,
298
313
  limit: validated.limit,
@@ -323,7 +338,7 @@ var Balances = class {
323
338
  */
324
339
  async transactions(params) {
325
340
  const validated = validateInput(listTransactionsSchema, params);
326
- const query = this.buildQueryString({
341
+ const query = buildQueryString({
327
342
  type: validated.type,
328
343
  limit: validated.limit,
329
344
  cursor: validated.cursor
@@ -340,42 +355,32 @@ var Balances = class {
340
355
  *
341
356
  * @example
342
357
  * ```typescript
343
- * const stats = await client.balances.stats("0x1234...")
358
+ * const stats = await client.balances.stats({
359
+ * walletAddress: "0x1234...",
360
+ * productId: "prod_123"
361
+ * })
344
362
  * console.log(`Total: ${stats.totalCredits}, Used: ${stats.totalUsed}`)
345
363
  * ```
346
364
  *
347
365
  * @throws {CloseLoopError} When input validation fails
348
366
  */
349
- async stats(walletAddress) {
350
- validateInput(walletAddressSchema, walletAddress);
351
- const query = this.buildQueryString({ walletAddress });
367
+ async stats(params) {
368
+ validateInput(getStatsSchema, params);
369
+ const query = buildQueryString({
370
+ walletAddress: params.walletAddress,
371
+ productId: params.productId
372
+ });
352
373
  return this.http.request({
353
374
  method: "GET",
354
375
  path: `${ENDPOINTS.STATS}?${query}`
355
376
  });
356
377
  }
357
- // ==========================================================================
358
- // Private Helpers
359
- // ==========================================================================
360
- /**
361
- * Build URL query string from params, filtering out undefined values
362
- */
363
- buildQueryString(params) {
364
- const query = new URLSearchParams();
365
- for (const [key, value] of Object.entries(params)) {
366
- if (value !== void 0) {
367
- query.set(key, String(value));
368
- }
369
- }
370
- return query.toString();
371
- }
372
378
  };
373
379
 
374
380
  // src/resources/credits.ts
375
381
  var ENDPOINTS2 = {
376
382
  VERIFY: "/api/credit/verify",
377
- CONSUME: "/api/credit/consume",
378
- BATCH_CONSUME: "/api/credit/consume/batch"
383
+ CONSUME: "/api/credit/consume"
379
384
  };
380
385
  var Credits = class {
381
386
  constructor(http) {
@@ -388,11 +393,10 @@ var Credits = class {
388
393
  * ```typescript
389
394
  * const result = await client.credits.verify({
390
395
  * walletAddress: "0x1234...",
391
- * planId: "plan_abc123",
392
396
  * amount: 10
393
397
  * })
394
398
  *
395
- * if (result.hasEnoughCredits) {
399
+ * if (result.hasEnough) {
396
400
  * // Proceed with service
397
401
  * }
398
402
  * ```
@@ -414,7 +418,6 @@ var Credits = class {
414
418
  * ```typescript
415
419
  * const result = await client.credits.consume({
416
420
  * walletAddress: "0x1234...",
417
- * planId: "plan_abc123",
418
421
  * amount: 1,
419
422
  * consumedBy: "ai-generation",
420
423
  * metadata: { requestId: "req_xyz" }
@@ -437,18 +440,22 @@ var Credits = class {
437
440
  });
438
441
  }
439
442
  /**
440
- * Verify and consume credits in a single atomic operation.
441
- * This is useful when you want to ensure credits are available
442
- * before consuming them, without race conditions.
443
+ * Verify and consume credits in a two-step operation.
444
+ *
445
+ * **Important**: This method performs TWO separate API calls (verify, then consume).
446
+ * There is a potential race condition between the verify and consume steps where
447
+ * another request could consume credits. For critical operations, consider:
448
+ * - Using an `idempotencyKey` to prevent duplicate consumption
449
+ * - Implementing server-side locking if your use case requires strict atomicity
443
450
  *
444
451
  * @example
445
452
  * ```typescript
446
453
  * try {
447
454
  * const result = await client.credits.verifyAndConsume({
448
455
  * walletAddress: "0x1234...",
449
- * planId: "plan_abc123",
450
456
  * amount: 5,
451
- * consumedBy: "batch-processing"
457
+ * consumedBy: "batch-processing",
458
+ * idempotencyKey: "unique-request-id" // Recommended for safety
452
459
  * })
453
460
  * // Credits were verified and consumed
454
461
  * } catch (error) {
@@ -469,8 +476,8 @@ var Credits = class {
469
476
  path: ENDPOINTS2.VERIFY,
470
477
  body: {
471
478
  walletAddress: validated.walletAddress,
472
- planId: validated.planId,
473
- amount: validated.amount
479
+ amount: validated.amount,
480
+ productId: validated.productId
474
481
  }
475
482
  });
476
483
  this.assertCreditsAvailable(verification, validated.amount);
@@ -481,39 +488,6 @@ var Credits = class {
481
488
  headers: this.buildIdempotencyHeaders(validated.idempotencyKey)
482
489
  });
483
490
  }
484
- /**
485
- * Consume credits in batch.
486
- *
487
- * @example
488
- * ```typescript
489
- * const result = await client.credits.batchConsume({
490
- * operations: [
491
- * { walletAddress: "0x1234...", planId: "plan_a", amount: 1 },
492
- * { walletAddress: "0x5678...", planId: "plan_b", amount: 2 }
493
- * ],
494
- * atomic: false // Allow partial success
495
- * })
496
- *
497
- * console.log(`Success: ${result.successCount}, Failed: ${result.failedCount}`)
498
- * ```
499
- *
500
- * @throws {CloseLoopError} When input validation fails
501
- */
502
- async batchConsume(params) {
503
- const validated = validateInput(batchConsumeSchema, params);
504
- const sanitizedOperations = validated.operations.map((op) => ({
505
- ...op,
506
- metadata: sanitizeMetadata(op.metadata)
507
- }));
508
- return this.http.request({
509
- method: "POST",
510
- path: ENDPOINTS2.BATCH_CONSUME,
511
- body: {
512
- operations: sanitizedOperations,
513
- atomic: validated.atomic
514
- }
515
- });
516
- }
517
491
  // ==========================================================================
518
492
  // Private Helpers
519
493
  // ==========================================================================
@@ -523,8 +497,8 @@ var Credits = class {
523
497
  buildConsumeBody(validated) {
524
498
  return {
525
499
  walletAddress: validated.walletAddress,
526
- planId: validated.planId,
527
500
  amount: validated.amount,
501
+ productId: validated.productId,
528
502
  consumedBy: validated.consumedBy,
529
503
  metadata: sanitizeMetadata(validated.metadata)
530
504
  };
@@ -572,7 +546,8 @@ function verifySignature(payload, signature, secret) {
572
546
  var ERROR_CODES = {
573
547
  VALIDATION: "VALIDATION_ERROR",
574
548
  INVALID_SIGNATURE: "INVALID_SIGNATURE",
575
- INVALID_PAYLOAD: "INVALID_PAYLOAD"
549
+ INVALID_PAYLOAD: "INVALID_PAYLOAD",
550
+ TIMESTAMP_EXPIRED: "TIMESTAMP_EXPIRED"
576
551
  };
577
552
  var Webhooks = class {
578
553
  /**
@@ -626,7 +601,10 @@ var Webhooks = class {
626
601
  const payloadString = this.normalizePayload(params.payload);
627
602
  this.verifySignatureOrThrow(payloadString, params.signature, params.secret);
628
603
  const parsed = this.parseJsonOrThrow(payloadString);
629
- return this.validateEventStructure(parsed);
604
+ const event = this.validateEventStructure(parsed);
605
+ const tolerance = params.toleranceSeconds ?? WEBHOOK_TIMESTAMP_TOLERANCE_SECONDS;
606
+ this.validateTimestamp(event.createdAt, tolerance);
607
+ return event;
630
608
  }
631
609
  /**
632
610
  * Type guard for payment.success events
@@ -746,7 +724,29 @@ var Webhooks = class {
746
724
  400
747
725
  );
748
726
  }
749
- return result.data;
727
+ const validatedData = result.data;
728
+ return {
729
+ id: validatedData.id,
730
+ type: validatedData.type,
731
+ createdAt: validatedData.createdAt,
732
+ data: validatedData.data
733
+ };
734
+ }
735
+ /**
736
+ * Validate webhook timestamp to prevent replay attacks
737
+ * Rejects webhooks older than the specified tolerance
738
+ */
739
+ validateTimestamp(createdAt, toleranceSeconds) {
740
+ const eventTime = new Date(createdAt).getTime();
741
+ const now = Date.now();
742
+ const ageSeconds = (now - eventTime) / 1e3;
743
+ if (ageSeconds > toleranceSeconds || ageSeconds < -60) {
744
+ throw new CloseLoopError(
745
+ `Webhook timestamp expired or invalid. Event age: ${Math.round(ageSeconds)}s, tolerance: ${toleranceSeconds}s`,
746
+ ERROR_CODES.TIMESTAMP_EXPIRED,
747
+ 400
748
+ );
749
+ }
750
750
  }
751
751
  };
752
752
 
@@ -847,8 +847,10 @@ var CloseLoop = class {
847
847
  */
848
848
  webhooks;
849
849
  constructor(options) {
850
- if (!options.apiKey) {
851
- throw new Error("CloseLoop API key is required");
850
+ if (!options.apiKey || typeof options.apiKey !== "string" || options.apiKey.trim().length < 10) {
851
+ throw new AuthenticationError(
852
+ "CloseLoop API key is required and must be at least 10 characters"
853
+ );
852
854
  }
853
855
  const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
854
856
  this.httpClient = new HttpClient({
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../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":["// Main client\nexport { CloseLoop } from \"./client\"\nexport type { CloseLoopOptions } from \"./client\"\n\n// Resources\nexport { Credits } from \"./resources/credits\"\nexport { Balances } from \"./resources/balances\"\nexport { Webhooks } from \"./resources/webhooks\"\nexport type { VerifyWebhookParams } from \"./resources/webhooks\"\n\n// Errors\nexport {\n CloseLoopError,\n InsufficientCreditsError,\n CreditsExpiredError,\n AuthenticationError,\n RateLimitError,\n NetworkError,\n NotFoundError,\n ValidationError\n} from \"./utils/errors\"\n\n// Utilities\nexport { validateInput } from \"./utils/validation\"\n\n// Credit types\nexport type {\n VerifyCreditsParams,\n VerifyCreditsResponse,\n ConsumeCreditsParams,\n ConsumeCreditsResponse,\n BatchConsumeParams,\n BatchConsumeResponse\n} from \"./types/credits\"\n\n// Balance types\nexport type {\n CreditBalance,\n GetBalanceParams,\n ListBalancesParams,\n ListBalancesResponse,\n CreditTransaction,\n ListTransactionsParams,\n ListTransactionsResponse,\n CreditStats\n} from \"./types/balances\"\n\n// Webhook types\nexport type {\n WebhookEventType,\n WebhookEvent,\n PaymentSuccessPayload,\n CreditsLowPayload,\n CreditsExpiredPayload,\n WebhookPayload\n} from \"./types/webhooks\"\n","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/index.ts","../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":["// Main client\nexport { CloseLoop } from \"./client\"\nexport type { CloseLoopOptions } from \"./client\"\n\n// Resources\nexport { Credits } from \"./resources/credits\"\nexport { Balances } from \"./resources/balances\"\nexport { Webhooks } from \"./resources/webhooks\"\nexport type { VerifyWebhookParams } from \"./resources/webhooks\"\n\n// Errors\nexport {\n CloseLoopError,\n InsufficientCreditsError,\n CreditsExpiredError,\n AuthenticationError,\n RateLimitError,\n NetworkError,\n NotFoundError,\n ValidationError\n} from \"./utils/errors\"\n\n// Utilities\nexport { validateInput } from \"./utils/validation\"\n\n// Re-export all types from barrel file\nexport type {\n // Credit types\n VerifyCreditsParams,\n VerifyCreditsResponse,\n ConsumeCreditsParams,\n ConsumeCreditsResponse,\n // Balance types\n CreditBalance,\n GetBalanceParams,\n ListBalancesParams,\n ListBalancesResponse,\n CreditTransaction,\n ListTransactionsParams,\n ListTransactionsResponse,\n CreditStats,\n // Webhook types\n WebhookEventType,\n WebhookEvent,\n PaymentSuccessPayload,\n CreditsLowPayload,\n CreditsExpiredPayload,\n WebhookPayload\n} from \"./types\"\n","// 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"]}