@closeloop/sdk 0.1.1 → 0.1.3

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 CHANGED
@@ -213,8 +213,8 @@ app.post("/webhook", (req, res) => {
213
213
 
214
214
  // Type-safe event handling
215
215
  if (client.webhooks.isPaymentSuccess(event)) {
216
- const { creditAmount, payerAddress, planId } = event.data
217
- console.log(`${payerAddress} purchased ${creditAmount} credits`)
216
+ const { type, walletAddress, planId, amount } = event.data
217
+ console.log(`${walletAddress} purchased plan ${planId} for $${amount} (${type})`)
218
218
  }
219
219
 
220
220
  if (client.webhooks.isCreditsLow(event)) {
@@ -257,55 +257,108 @@ export async function POST(request: Request) {
257
257
 
258
258
  ## Next.js Integration
259
259
 
260
- ### Middleware
260
+ ### Proxy (Next.js 16+)
261
261
 
262
- Protect routes by requiring credits:
262
+ Protect routes by verifying credits:
263
263
 
264
264
  ```typescript
265
- // middleware.ts
266
- import { CloseLoop, creditGate } from "@closeloop/sdk/nextjs"
265
+ // proxy.ts (Next.js 16+)
266
+ import { NextRequest, NextResponse } from "next/server"
267
+ import { CloseLoop } from "@closeloop/sdk"
267
268
 
268
269
  const client = new CloseLoop({ apiKey: process.env.CLOSELOOP_API_KEY! })
269
270
 
270
- export default creditGate({
271
- client,
272
- planId: "plan_abc123",
273
- creditsPerRequest: 1,
274
- getWalletAddress: (req) => req.headers.get("x-wallet-address")
275
- })
271
+ // Define routes that require credit verification
272
+ const creditProtectedRoutes = ["/api/ai", "/api/generate"]
273
+
274
+ export default async function proxy(req: NextRequest) {
275
+ const path = req.nextUrl.pathname
276
+ const isCreditProtectedRoute = creditProtectedRoutes.some(route =>
277
+ path.startsWith(route)
278
+ )
279
+
280
+ if (!isCreditProtectedRoute) {
281
+ return NextResponse.next()
282
+ }
283
+
284
+ // Get wallet address from header or session
285
+ const walletAddress = req.headers.get("x-wallet-address")
286
+
287
+ if (!walletAddress) {
288
+ return NextResponse.json(
289
+ { error: "Wallet address required" },
290
+ { status: 401 }
291
+ )
292
+ }
293
+
294
+ // Verify credits before allowing the request
295
+ const verification = await client.credits.verify({
296
+ walletAddress,
297
+ planId: "plan_abc123",
298
+ amount: 1
299
+ })
300
+
301
+ if (!verification.hasEnoughCredits) {
302
+ return NextResponse.json(
303
+ { error: "Insufficient credits" },
304
+ { status: 402 }
305
+ )
306
+ }
307
+
308
+ return NextResponse.next()
309
+ }
276
310
 
277
311
  export const config = {
278
- matcher: ["/api/protected/:path*"]
312
+ matcher: ["/((?!_next/static|_next/image|.*\\.png$).*)"]
279
313
  }
280
314
  ```
281
315
 
282
- ### API Route Helper
316
+ ### Route Handler
283
317
 
284
- Consume credits after processing:
318
+ Verify and consume credits atomically in API routes:
285
319
 
286
320
  ```typescript
287
- // app/api/ai/route.ts
321
+ // app/api/ai/route.ts (Next.js 16+)
288
322
  import { NextRequest, NextResponse } from "next/server"
289
- import { CloseLoop, consumeCreditsAfterRequest } from "@closeloop/sdk/nextjs"
323
+ import { CloseLoop, InsufficientCreditsError } from "@closeloop/sdk"
290
324
 
291
325
  const client = new CloseLoop({ apiKey: process.env.CLOSELOOP_API_KEY! })
292
326
 
293
327
  export async function POST(request: NextRequest) {
294
- const wallet = request.headers.get("x-wallet-address")!
295
-
296
- // Process request...
297
- const result = await processAIRequest()
328
+ const wallet = request.headers.get("x-wallet-address")
298
329
 
299
- // Consume credit after successful processing
300
- await consumeCreditsAfterRequest(client, {
301
- walletAddress: wallet,
302
- planId: "plan_abc123",
303
- amount: 1,
304
- consumedBy: "ai-text-generation",
305
- metadata: { requestId: result.id }
306
- })
330
+ if (!wallet) {
331
+ return NextResponse.json(
332
+ { error: "Wallet address required" },
333
+ { status: 401 }
334
+ )
335
+ }
307
336
 
308
- return NextResponse.json(result)
337
+ // Verify and consume credits atomically
338
+ try {
339
+ const result = await client.credits.verifyAndConsume({
340
+ walletAddress: wallet,
341
+ planId: "plan_abc123",
342
+ amount: 1,
343
+ consumedBy: "ai-text-generation"
344
+ })
345
+
346
+ // Process request after credit verification
347
+ const aiResult = await processAIRequest()
348
+
349
+ return NextResponse.json({
350
+ ...aiResult,
351
+ remainingCredits: result.remainingCredits
352
+ })
353
+ } catch (error) {
354
+ if (error instanceof InsufficientCreditsError) {
355
+ return NextResponse.json(
356
+ { error: "Insufficient credits", remaining: error.remainingCredits },
357
+ { status: 402 }
358
+ )
359
+ }
360
+ throw error
361
+ }
309
362
  }
310
363
  ```
311
364
 
@@ -369,11 +422,12 @@ All inputs are validated using Zod schemas before being sent to the API:
369
422
 
370
423
  ### Rate Limiting
371
424
 
372
- The `creditGate` middleware does **not** include rate limiting. We strongly recommend combining it with a rate limiter:
425
+ The proxy does **not** include rate limiting. We strongly recommend combining it with a rate limiter:
373
426
 
374
427
  ```typescript
375
- // middleware.ts - With rate limiting (RECOMMENDED)
376
- import { CloseLoop, creditGate } from "@closeloop/sdk/nextjs"
428
+ // proxy.ts - With rate limiting (RECOMMENDED)
429
+ import { NextRequest, NextResponse } from "next/server"
430
+ import { CloseLoop } from "@closeloop/sdk"
377
431
  import { Ratelimit } from "@upstash/ratelimit"
378
432
  import { Redis } from "@upstash/redis"
379
433
 
@@ -381,31 +435,59 @@ const client = new CloseLoop({ apiKey: process.env.CLOSELOOP_API_KEY! })
381
435
 
382
436
  const ratelimit = new Ratelimit({
383
437
  redis: Redis.fromEnv(),
384
- limiter: Ratelimit.slidingWindow(10, "10 s"), // 10 requests per 10 seconds
438
+ limiter: Ratelimit.slidingWindow(10, "10 s"),
385
439
  })
386
440
 
387
- const creditGateMiddleware = creditGate({
388
- client,
389
- planId: "plan_abc123",
390
- creditsPerRequest: 1,
391
- getWalletAddress: (req) => req.headers.get("x-wallet-address")
392
- })
441
+ const creditProtectedRoutes = ["/api/ai", "/api/generate"]
442
+
443
+ export default async function proxy(request: NextRequest) {
444
+ const path = request.nextUrl.pathname
445
+ const isCreditProtectedRoute = creditProtectedRoutes.some(route =>
446
+ path.startsWith(route)
447
+ )
448
+
449
+ if (!isCreditProtectedRoute) {
450
+ return NextResponse.next()
451
+ }
393
452
 
394
- export default async function middleware(request: NextRequest) {
395
453
  // Rate limit by IP first
396
- const ip = request.ip ?? "127.0.0.1"
454
+ const ip = request.headers.get("x-forwarded-for") ?? "127.0.0.1"
397
455
  const { success } = await ratelimit.limit(ip)
398
456
 
399
457
  if (!success) {
400
- return NextResponse.json({ error: "Rate limit exceeded" }, { status: 429 })
458
+ return NextResponse.json(
459
+ { error: "Rate limit exceeded" },
460
+ { status: 429 }
461
+ )
462
+ }
463
+
464
+ // Then verify credits
465
+ const walletAddress = request.headers.get("x-wallet-address")
466
+ if (!walletAddress) {
467
+ return NextResponse.json(
468
+ { error: "Wallet address required" },
469
+ { status: 401 }
470
+ )
471
+ }
472
+
473
+ const verification = await client.credits.verify({
474
+ walletAddress,
475
+ planId: "plan_abc123",
476
+ amount: 1
477
+ })
478
+
479
+ if (!verification.hasEnoughCredits) {
480
+ return NextResponse.json(
481
+ { error: "Insufficient credits" },
482
+ { status: 402 }
483
+ )
401
484
  }
402
485
 
403
- // Then check credits
404
- return creditGateMiddleware(request)
486
+ return NextResponse.next()
405
487
  }
406
488
 
407
489
  export const config = {
408
- matcher: ["/api/protected/:path*"]
490
+ matcher: ["/((?!_next/static|_next/image|.*\\.png$).*)"]
409
491
  }
410
492
  ```
411
493
 
@@ -568,9 +568,9 @@ interface WebhookEvent<T = WebhookPayload> {
568
568
  */
569
569
  interface PaymentSuccessPayload {
570
570
  /**
571
- * Transaction ID
571
+ * Access type of the plan (API, CREDIT, DISCORD, etc.)
572
572
  */
573
- transactionId: string;
573
+ type: string;
574
574
  /**
575
575
  * Plan ID that was purchased
576
576
  */
@@ -584,25 +584,17 @@ interface PaymentSuccessPayload {
584
584
  */
585
585
  amount: number;
586
586
  /**
587
- * Number of API requests (for API access type)
588
- */
589
- requests: number | null;
590
- /**
591
- * Number of credits (for CREDIT access type)
592
- */
593
- creditAmount: number | null;
594
- /**
595
- * Blockchain transaction hash
587
+ * Wallet address of the payer
596
588
  */
597
- transactionHash: string;
589
+ walletAddress: string;
598
590
  /**
599
- * Wallet address of the payer
591
+ * Transaction ID
600
592
  */
601
- payerAddress: string;
593
+ transactionId: string;
602
594
  /**
603
- * Access type of the plan
595
+ * Allow additional properties for extensibility
604
596
  */
605
- accessType: string;
597
+ [key: string]: unknown;
606
598
  }
607
599
  /**
608
600
  * Payload for credits.low events
@@ -687,8 +679,8 @@ declare class Webhooks {
687
679
  * })
688
680
  *
689
681
  * if (client.webhooks.isPaymentSuccess(event)) {
690
- * const { creditAmount, payerAddress } = event.data
691
- * // Handle credit purchase
682
+ * const { type, walletAddress, planId, amount } = event.data
683
+ * // Handle payment success
692
684
  * }
693
685
  *
694
686
  * res.json({ received: true })
@@ -727,7 +719,7 @@ declare class Webhooks {
727
719
  * ```typescript
728
720
  * if (client.webhooks.isPaymentSuccess(event)) {
729
721
  * // TypeScript knows event.data is PaymentSuccessPayload
730
- * console.log(event.data.creditAmount)
722
+ * console.log(event.data.planId, event.data.walletAddress)
731
723
  * }
732
724
  * ```
733
725
  */
@@ -568,9 +568,9 @@ interface WebhookEvent<T = WebhookPayload> {
568
568
  */
569
569
  interface PaymentSuccessPayload {
570
570
  /**
571
- * Transaction ID
571
+ * Access type of the plan (API, CREDIT, DISCORD, etc.)
572
572
  */
573
- transactionId: string;
573
+ type: string;
574
574
  /**
575
575
  * Plan ID that was purchased
576
576
  */
@@ -584,25 +584,17 @@ interface PaymentSuccessPayload {
584
584
  */
585
585
  amount: number;
586
586
  /**
587
- * Number of API requests (for API access type)
588
- */
589
- requests: number | null;
590
- /**
591
- * Number of credits (for CREDIT access type)
592
- */
593
- creditAmount: number | null;
594
- /**
595
- * Blockchain transaction hash
587
+ * Wallet address of the payer
596
588
  */
597
- transactionHash: string;
589
+ walletAddress: string;
598
590
  /**
599
- * Wallet address of the payer
591
+ * Transaction ID
600
592
  */
601
- payerAddress: string;
593
+ transactionId: string;
602
594
  /**
603
- * Access type of the plan
595
+ * Allow additional properties for extensibility
604
596
  */
605
- accessType: string;
597
+ [key: string]: unknown;
606
598
  }
607
599
  /**
608
600
  * Payload for credits.low events
@@ -687,8 +679,8 @@ declare class Webhooks {
687
679
  * })
688
680
  *
689
681
  * if (client.webhooks.isPaymentSuccess(event)) {
690
- * const { creditAmount, payerAddress } = event.data
691
- * // Handle credit purchase
682
+ * const { type, walletAddress, planId, amount } = event.data
683
+ * // Handle payment success
692
684
  * }
693
685
  *
694
686
  * res.json({ received: true })
@@ -727,7 +719,7 @@ declare class Webhooks {
727
719
  * ```typescript
728
720
  * if (client.webhooks.isPaymentSuccess(event)) {
729
721
  * // TypeScript knows event.data is PaymentSuccessPayload
730
- * console.log(event.data.creditAmount)
722
+ * console.log(event.data.planId, event.data.walletAddress)
731
723
  * }
732
724
  * ```
733
725
  */
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AuthenticationError, B as Balances, k as BatchConsumeParams, l as BatchConsumeResponse, C as CloseLoop, c as CloseLoopError, a as CloseLoopOptions, i as ConsumeCreditsParams, j as ConsumeCreditsResponse, m as CreditBalance, r as CreditStats, o as CreditTransaction, b as Credits, d as CreditsExpiredError, v as CreditsExpiredPayload, u as CreditsLowPayload, G as GetBalanceParams, I as InsufficientCreditsError, L as ListBalancesParams, n as ListBalancesResponse, p as ListTransactionsParams, q as ListTransactionsResponse, N as NetworkError, e as NotFoundError, P as PaymentSuccessPayload, R as RateLimitError, f as ValidationError, g as VerifyCreditsParams, h as VerifyCreditsResponse, V as VerifyWebhookParams, t as WebhookEvent, s as WebhookEventType, w as WebhookPayload, W as Webhooks } from './errors-B0i1OvBd.mjs';
1
+ export { A as AuthenticationError, B as Balances, k as BatchConsumeParams, l as BatchConsumeResponse, C as CloseLoop, c as CloseLoopError, a as CloseLoopOptions, i as ConsumeCreditsParams, j as ConsumeCreditsResponse, m as CreditBalance, r as CreditStats, o as CreditTransaction, b as Credits, d as CreditsExpiredError, v as CreditsExpiredPayload, u as CreditsLowPayload, G as GetBalanceParams, I as InsufficientCreditsError, L as ListBalancesParams, n as ListBalancesResponse, p as ListTransactionsParams, q as ListTransactionsResponse, N as NetworkError, e as NotFoundError, P as PaymentSuccessPayload, R as RateLimitError, f as ValidationError, g as VerifyCreditsParams, h as VerifyCreditsResponse, V as VerifyWebhookParams, t as WebhookEvent, s as WebhookEventType, w as WebhookPayload, W as Webhooks } from './errors-CmMqVxNU.mjs';
2
2
 
3
3
  /**
4
4
  * Generic schema interface for validation
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AuthenticationError, B as Balances, k as BatchConsumeParams, l as BatchConsumeResponse, C as CloseLoop, c as CloseLoopError, a as CloseLoopOptions, i as ConsumeCreditsParams, j as ConsumeCreditsResponse, m as CreditBalance, r as CreditStats, o as CreditTransaction, b as Credits, d as CreditsExpiredError, v as CreditsExpiredPayload, u as CreditsLowPayload, G as GetBalanceParams, I as InsufficientCreditsError, L as ListBalancesParams, n as ListBalancesResponse, p as ListTransactionsParams, q as ListTransactionsResponse, N as NetworkError, e as NotFoundError, P as PaymentSuccessPayload, R as RateLimitError, f as ValidationError, g as VerifyCreditsParams, h as VerifyCreditsResponse, V as VerifyWebhookParams, t as WebhookEvent, s as WebhookEventType, w as WebhookPayload, W as Webhooks } from './errors-B0i1OvBd.js';
1
+ export { A as AuthenticationError, B as Balances, k as BatchConsumeParams, l as BatchConsumeResponse, C as CloseLoop, c as CloseLoopError, a as CloseLoopOptions, i as ConsumeCreditsParams, j as ConsumeCreditsResponse, m as CreditBalance, r as CreditStats, o as CreditTransaction, b as Credits, d as CreditsExpiredError, v as CreditsExpiredPayload, u as CreditsLowPayload, G as GetBalanceParams, I as InsufficientCreditsError, L as ListBalancesParams, n as ListBalancesResponse, p as ListTransactionsParams, q as ListTransactionsResponse, N as NetworkError, e as NotFoundError, P as PaymentSuccessPayload, R as RateLimitError, f as ValidationError, g as VerifyCreditsParams, h as VerifyCreditsResponse, V as VerifyWebhookParams, t as WebhookEvent, s as WebhookEventType, w as WebhookPayload, W as Webhooks } from './errors-CmMqVxNU.js';
2
2
 
3
3
  /**
4
4
  * Generic schema interface for validation
package/dist/index.js CHANGED
@@ -122,7 +122,7 @@ var paymentSuccessPayloadSchema = import_zod.z.object({
122
122
  planId: import_zod.z.string(),
123
123
  planName: import_zod.z.string(),
124
124
  amount: import_zod.z.number(),
125
- wallet: import_zod.z.string(),
125
+ walletAddress: import_zod.z.string(),
126
126
  transactionId: import_zod.z.string()
127
127
  }).loose();
128
128
  var creditsLowPayloadSchema = import_zod.z.object({
@@ -590,8 +590,8 @@ var Webhooks = class {
590
590
  * })
591
591
  *
592
592
  * if (client.webhooks.isPaymentSuccess(event)) {
593
- * const { creditAmount, payerAddress } = event.data
594
- * // Handle credit purchase
593
+ * const { type, walletAddress, planId, amount } = event.data
594
+ * // Handle payment success
595
595
  * }
596
596
  *
597
597
  * res.json({ received: true })
@@ -636,7 +636,7 @@ var Webhooks = class {
636
636
  * ```typescript
637
637
  * if (client.webhooks.isPaymentSuccess(event)) {
638
638
  * // TypeScript knows event.data is PaymentSuccessPayload
639
- * console.log(event.data.creditAmount)
639
+ * console.log(event.data.planId, event.data.walletAddress)
640
640
  * }
641
641
  * ```
642
642
  */
@@ -773,7 +773,7 @@ var HttpClient = class {
773
773
  headers: {
774
774
  "Content-Type": "application/json",
775
775
  "User-Agent": USER_AGENT,
776
- "X-API-Key": this.apiKey,
776
+ Authorization: `Bearer ${this.apiKey}`,
777
777
  ...options.headers
778
778
  },
779
779
  body: options.body ? JSON.stringify(options.body) : void 0,