@closeloop/sdk 0.1.7 → 0.1.8
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/README.md +18 -234
- package/dist/index.d.mts +843 -2
- package/dist/index.d.ts +843 -2
- package/dist/index.js +107 -105
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +107 -105
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -19
- package/dist/errors-CNnLzjDZ.d.mts +0 -901
- package/dist/errors-CNnLzjDZ.d.ts +0 -901
- package/dist/nextjs.d.mts +0 -135
- package/dist/nextjs.d.ts +0 -135
- package/dist/nextjs.js +0 -952
- package/dist/nextjs.js.map +0 -1
- package/dist/nextjs.mjs +0 -917
- package/dist/nextjs.mjs.map +0 -1
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.8" : "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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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 =
|
|
228
|
+
const query = buildQueryString({
|
|
214
229
|
walletAddress: validated.walletAddress,
|
|
215
|
-
|
|
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.
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
312
|
-
validateInput(
|
|
313
|
-
const query =
|
|
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.
|
|
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
|
|
403
|
-
*
|
|
404
|
-
*
|
|
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
|
-
|
|
435
|
-
|
|
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
|
};
|
|
@@ -502,9 +476,9 @@ var Credits = class {
|
|
|
502
476
|
* Assert credits are available, throw typed errors if not
|
|
503
477
|
*/
|
|
504
478
|
assertCreditsAvailable(verification, requiredAmount) {
|
|
505
|
-
if (!verification.
|
|
479
|
+
if (!verification.hasEnough) {
|
|
506
480
|
throw new InsufficientCreditsError(
|
|
507
|
-
verification.
|
|
481
|
+
verification.totalRemaining,
|
|
508
482
|
requiredAmount
|
|
509
483
|
);
|
|
510
484
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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({
|
package/dist/index.mjs.map
CHANGED
|
@@ -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.hasEnough) {\n throw new InsufficientCreditsError(\n verification.totalRemaining,\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"]}
|