@closeloop/sdk 0.1.6 → 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 CHANGED
@@ -28,7 +28,7 @@ const client = new CloseLoop({
28
28
  // Verify credits before processing
29
29
  const verification = await client.credits.verify({
30
30
  walletAddress: "0x1234...",
31
- planId: "plan_abc123",
31
+ productId: "prod_01",
32
32
  amount: 1
33
33
  })
34
34
 
@@ -38,7 +38,7 @@ if (verification.hasEnoughCredits) {
38
38
  // Consume the credit
39
39
  await client.credits.consume({
40
40
  walletAddress: "0x1234...",
41
- planId: "plan_abc123",
41
+ productId: "prod_01",
42
42
  amount: 1,
43
43
  consumedBy: "my-service"
44
44
  })
@@ -51,7 +51,6 @@ if (verification.hasEnoughCredits) {
51
51
  - ⚡ **Fast** - Lightweight with minimal dependencies
52
52
  - 🔄 **Atomic operations** - Verify and consume in one call
53
53
  - 🪝 **Webhook support** - Secure signature verification with schema validation
54
- - 📦 **Framework helpers** - Next.js middleware with rate limiting docs
55
54
  - ✅ **Validated** - All inputs validated with Zod schemas
56
55
 
57
56
  ## API Reference
@@ -65,12 +64,12 @@ Check if a user has enough credits without consuming them:
65
64
  ```typescript
66
65
  const result = await client.credits.verify({
67
66
  walletAddress: "0x...",
68
- planId: "plan_id",
67
+ productId: "prod_id",
69
68
  amount: 10
70
69
  })
71
70
 
72
- console.log(result.hasEnoughCredits) // true
73
- console.log(result.remainingCredits) // 100
71
+ console.log(result.hasEnough) // true
72
+ console.log(result.totalRemaining) // 100
74
73
  console.log(result.expiresAt) // "2024-12-31T23:59:59Z" or null
75
74
  ```
76
75
 
@@ -81,7 +80,7 @@ Deduct credits from a user's balance:
81
80
  ```typescript
82
81
  const result = await client.credits.consume({
83
82
  walletAddress: "0x...",
84
- planId: "plan_id",
83
+ productId: "prod_id",
85
84
  amount: 1,
86
85
  consumedBy: "ai-generation", // Optional: track what used the credits
87
86
  metadata: { requestId: "req_123" }, // Optional: attach metadata
@@ -89,7 +88,7 @@ const result = await client.credits.consume({
89
88
  })
90
89
 
91
90
  console.log(result.success) // true
92
- console.log(result.remainingCredits) // 99
91
+ console.log(result.totalRemaining) // 99
93
92
  console.log(result.transactionId) // "tx_abc123"
94
93
  ```
95
94
 
@@ -103,7 +102,7 @@ import { InsufficientCreditsError, CreditsExpiredError } from "@closeloop/sdk"
103
102
  try {
104
103
  const result = await client.credits.verifyAndConsume({
105
104
  walletAddress: "0x...",
106
- planId: "plan_id",
105
+ productId: "prod_id",
107
106
  amount: 5,
108
107
  consumedBy: "batch-processing"
109
108
  })
@@ -117,22 +116,6 @@ try {
117
116
  }
118
117
  ```
119
118
 
120
- #### Batch Consume
121
-
122
- Consume credits for multiple users in one request:
123
-
124
- ```typescript
125
- const result = await client.credits.batchConsume({
126
- operations: [
127
- { walletAddress: "0x1234...", planId: "plan_a", amount: 1 },
128
- { walletAddress: "0x5678...", planId: "plan_b", amount: 2 }
129
- ],
130
- atomic: false // Allow partial success
131
- })
132
-
133
- console.log(`Success: ${result.successCount}, Failed: ${result.failedCount}`)
134
- ```
135
-
136
119
  ### Balance Queries
137
120
 
138
121
  #### Get Specific Balance
@@ -140,7 +123,7 @@ console.log(`Success: ${result.successCount}, Failed: ${result.failedCount}`)
140
123
  ```typescript
141
124
  const balance = await client.balances.get({
142
125
  walletAddress: "0x...",
143
- planId: "plan_id"
126
+ productId: "prod_id"
144
127
  })
145
128
 
146
129
  if (balance) {
@@ -159,8 +142,9 @@ const { balances, nextCursor, totalCount } = await client.balances.list({
159
142
  limit: 10
160
143
  })
161
144
 
145
+
162
146
  for (const balance of balances) {
163
- console.log(`${balance.planName}: ${balance.remainingCredits} credits`)
147
+ console.log(`${balance.productName}: ${balance.remainingCredits} credits`)
164
148
  }
165
149
 
166
150
  // Paginate
@@ -189,12 +173,14 @@ for (const tx of transactions) {
189
173
  #### Get Aggregated Stats
190
174
 
191
175
  ```typescript
192
- const stats = await client.balances.stats("0x...")
176
+ const stats = await client.balances.stats({
177
+ walletAddress: "0x...",
178
+ productId: "prod_id"
179
+ })
193
180
 
194
181
  console.log(`Total: ${stats.totalCredits}`)
195
182
  console.log(`Used: ${stats.totalUsed}`)
196
183
  console.log(`Remaining: ${stats.totalRemaining}`)
197
- console.log(`Active Balances: ${stats.activeBalances}`)
198
184
  ```
199
185
 
200
186
  ### Webhook Verification
@@ -213,8 +199,8 @@ app.post("/webhook", (req, res) => {
213
199
 
214
200
  // Type-safe event handling
215
201
  if (client.webhooks.isPaymentSuccess(event)) {
216
- const { type, walletAddress, planId, amount } = event.data
217
- console.log(`${walletAddress} purchased plan ${planId} for $${amount} (${type})`)
202
+ const { type, walletAddress, productId, amount } = event.data
203
+ console.log(`${walletAddress} purchased plan ${productId} for $${amount} (${type})`)
218
204
  }
219
205
 
220
206
  if (client.webhooks.isCreditsLow(event)) {
@@ -234,134 +220,6 @@ app.post("/webhook", (req, res) => {
234
220
  })
235
221
  ```
236
222
 
237
- ```typescript
238
- // Next.js App Router
239
- export async function POST(request: Request) {
240
- const payload = await request.text()
241
- const signature = request.headers.get("x-closeloop-signature")!
242
-
243
- try {
244
- const event = client.webhooks.verify({
245
- payload,
246
- signature,
247
- secret: process.env.WEBHOOK_SECRET!
248
- })
249
-
250
- // Handle event...
251
- return Response.json({ received: true })
252
- } catch {
253
- return Response.json({ error: "Invalid signature" }, { status: 400 })
254
- }
255
- }
256
- ```
257
-
258
- ## Next.js Integration
259
-
260
- ### Proxy (Next.js 16+)
261
-
262
- Protect routes by verifying credits:
263
-
264
- ```typescript
265
- // proxy.ts (Next.js 16+)
266
- import { NextRequest, NextResponse } from "next/server"
267
- import { CloseLoop } from "@closeloop/sdk"
268
-
269
- const client = new CloseLoop({ apiKey: process.env.CLOSELOOP_API_KEY! })
270
-
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
- }
310
-
311
- export const config = {
312
- matcher: ["/((?!_next/static|_next/image|.*\\.png$).*)"]
313
- }
314
- ```
315
-
316
- ### Route Handler
317
-
318
- Verify and consume credits atomically in API routes:
319
-
320
- ```typescript
321
- // app/api/ai/route.ts (Next.js 16+)
322
- import { NextRequest, NextResponse } from "next/server"
323
- import { CloseLoop, InsufficientCreditsError } from "@closeloop/sdk"
324
-
325
- const client = new CloseLoop({ apiKey: process.env.CLOSELOOP_API_KEY! })
326
-
327
- export async function POST(request: NextRequest) {
328
- const wallet = request.headers.get("x-wallet-address")
329
-
330
- if (!wallet) {
331
- return NextResponse.json(
332
- { error: "Wallet address required" },
333
- { status: 401 }
334
- )
335
- }
336
-
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
- }
362
- }
363
- ```
364
-
365
223
  ## Error Handling
366
224
 
367
225
  All errors extend `CloseLoopError`:
@@ -416,81 +274,10 @@ try {
416
274
  All inputs are validated using Zod schemas before being sent to the API:
417
275
 
418
276
  - **Wallet addresses**: Must be valid Ethereum addresses (`0x` + 40 hex chars)
419
- - **Plan IDs**: Non-empty strings with max 100 characters
277
+ - **Product IDs**: Non-empty strings with max 100 characters
420
278
  - **Credit amounts**: Positive integers up to 1 billion
421
279
  - **Metadata**: Sanitized to prevent prototype pollution attacks
422
280
 
423
- ### Rate Limiting
424
-
425
- The proxy does **not** include rate limiting. We strongly recommend combining it with a rate limiter:
426
-
427
- ```typescript
428
- // proxy.ts - With rate limiting (RECOMMENDED)
429
- import { NextRequest, NextResponse } from "next/server"
430
- import { CloseLoop } from "@closeloop/sdk"
431
- import { Ratelimit } from "@upstash/ratelimit"
432
- import { Redis } from "@upstash/redis"
433
-
434
- const client = new CloseLoop({ apiKey: process.env.CLOSELOOP_API_KEY! })
435
-
436
- const ratelimit = new Ratelimit({
437
- redis: Redis.fromEnv(),
438
- limiter: Ratelimit.slidingWindow(10, "10 s"),
439
- })
440
-
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
- }
452
-
453
- // Rate limit by IP first
454
- const ip = request.headers.get("x-forwarded-for") ?? "127.0.0.1"
455
- const { success } = await ratelimit.limit(ip)
456
-
457
- if (!success) {
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
- )
484
- }
485
-
486
- return NextResponse.next()
487
- }
488
-
489
- export const config = {
490
- matcher: ["/((?!_next/static|_next/image|.*\\.png$).*)"]
491
- }
492
- ```
493
-
494
281
  ### Webhook Security
495
282
 
496
283
  - HMAC-SHA256 signatures with timing-safe comparison
@@ -511,8 +298,6 @@ import type {
511
298
  VerifyCreditsResponse,
512
299
  ConsumeCreditsParams,
513
300
  ConsumeCreditsResponse,
514
- BatchConsumeParams,
515
- BatchConsumeResponse,
516
301
 
517
302
  // Balances
518
303
  CreditBalance,
@@ -553,7 +338,6 @@ const client = new CloseLoop({
553
338
  ## Requirements
554
339
 
555
340
  - Node.js 18.0.0 or higher
556
- - Next.js 14.0.0 or higher (for Next.js helpers)
557
341
 
558
342
  ## License
559
343