@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 +128 -46
- package/dist/{errors-B0i1OvBd.d.mts → errors-CmMqVxNU.d.mts} +11 -19
- package/dist/{errors-B0i1OvBd.d.ts → errors-CmMqVxNU.d.ts} +11 -19
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5 -5
- package/dist/index.mjs.map +1 -1
- package/dist/nextjs.d.mts +2 -2
- package/dist/nextjs.d.ts +2 -2
- package/dist/nextjs.js +5 -5
- package/dist/nextjs.js.map +1 -1
- package/dist/nextjs.mjs +5 -5
- package/dist/nextjs.mjs.map +1 -1
- package/package.json +1 -1
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 {
|
|
217
|
-
console.log(`${
|
|
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
|
-
###
|
|
260
|
+
### Proxy (Next.js 16+)
|
|
261
261
|
|
|
262
|
-
Protect routes by
|
|
262
|
+
Protect routes by verifying credits:
|
|
263
263
|
|
|
264
264
|
```typescript
|
|
265
|
-
//
|
|
266
|
-
import {
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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: ["/
|
|
312
|
+
matcher: ["/((?!_next/static|_next/image|.*\\.png$).*)"]
|
|
279
313
|
}
|
|
280
314
|
```
|
|
281
315
|
|
|
282
|
-
###
|
|
316
|
+
### Route Handler
|
|
283
317
|
|
|
284
|
-
|
|
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,
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
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
|
|
425
|
+
The proxy does **not** include rate limiting. We strongly recommend combining it with a rate limiter:
|
|
373
426
|
|
|
374
427
|
```typescript
|
|
375
|
-
//
|
|
376
|
-
import {
|
|
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"),
|
|
438
|
+
limiter: Ratelimit.slidingWindow(10, "10 s"),
|
|
385
439
|
})
|
|
386
440
|
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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.
|
|
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(
|
|
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
|
-
|
|
404
|
-
return creditGateMiddleware(request)
|
|
486
|
+
return NextResponse.next()
|
|
405
487
|
}
|
|
406
488
|
|
|
407
489
|
export const config = {
|
|
408
|
-
matcher: ["/
|
|
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
|
-
*
|
|
571
|
+
* Access type of the plan (API, CREDIT, DISCORD, etc.)
|
|
572
572
|
*/
|
|
573
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
589
|
+
walletAddress: string;
|
|
598
590
|
/**
|
|
599
|
-
*
|
|
591
|
+
* Transaction ID
|
|
600
592
|
*/
|
|
601
|
-
|
|
593
|
+
transactionId: string;
|
|
602
594
|
/**
|
|
603
|
-
*
|
|
595
|
+
* Allow additional properties for extensibility
|
|
604
596
|
*/
|
|
605
|
-
|
|
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 {
|
|
691
|
-
* // Handle
|
|
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.
|
|
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
|
-
*
|
|
571
|
+
* Access type of the plan (API, CREDIT, DISCORD, etc.)
|
|
572
572
|
*/
|
|
573
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
589
|
+
walletAddress: string;
|
|
598
590
|
/**
|
|
599
|
-
*
|
|
591
|
+
* Transaction ID
|
|
600
592
|
*/
|
|
601
|
-
|
|
593
|
+
transactionId: string;
|
|
602
594
|
/**
|
|
603
|
-
*
|
|
595
|
+
* Allow additional properties for extensibility
|
|
604
596
|
*/
|
|
605
|
-
|
|
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 {
|
|
691
|
-
* // Handle
|
|
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.
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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 {
|
|
594
|
-
* // Handle
|
|
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.
|
|
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
|
-
|
|
776
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
777
777
|
...options.headers
|
|
778
778
|
},
|
|
779
779
|
body: options.body ? JSON.stringify(options.body) : void 0,
|