@zendfi/sdk 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -4,43 +4,20 @@ import {
4
4
  } from "./chunk-YFOBPGQE.mjs";
5
5
 
6
6
  // src/client.ts
7
- import fetch from "cross-fetch";
7
+ import fetch2 from "cross-fetch";
8
8
  import { createHmac, timingSafeEqual } from "crypto";
9
9
 
10
10
  // src/types.ts
11
- var ZendFiError = class extends Error {
12
- constructor(message, statusCode, code, details) {
13
- super(message);
14
- this.statusCode = statusCode;
15
- this.code = code;
16
- this.details = details;
17
- this.name = "ZendFiError";
18
- }
19
- };
20
- var AuthenticationError = class extends ZendFiError {
21
- constructor(message = "Authentication failed") {
22
- super(message, 401, "AUTHENTICATION_ERROR");
23
- this.name = "AuthenticationError";
24
- }
25
- };
26
- var ValidationError = class extends ZendFiError {
27
- constructor(message, details) {
28
- super(message, 400, "VALIDATION_ERROR", details);
29
- this.name = "ValidationError";
30
- }
31
- };
32
- var NetworkError = class extends ZendFiError {
33
- constructor(message) {
34
- super(message, 0, "NETWORK_ERROR");
35
- this.name = "NetworkError";
36
- }
37
- };
38
- var RateLimitError = class extends ZendFiError {
39
- constructor(message = "Rate limit exceeded") {
40
- super(message, 429, "RATE_LIMIT_ERROR");
41
- this.name = "RateLimitError";
42
- }
43
- };
11
+ var asPaymentId = (id) => id;
12
+ var asSessionId = (id) => id;
13
+ var asAgentKeyId = (id) => id;
14
+ var asMerchantId = (id) => id;
15
+ var asInvoiceId = (id) => id;
16
+ var asSubscriptionId = (id) => id;
17
+ var asEscrowId = (id) => id;
18
+ var asInstallmentPlanId = (id) => id;
19
+ var asPaymentLinkCode = (id) => id;
20
+ var asIntentId = (id) => id;
44
21
 
45
22
  // src/utils.ts
46
23
  var ConfigLoader = class {
@@ -59,7 +36,8 @@ var ConfigLoader = class {
59
36
  mode,
60
37
  timeout: options?.timeout ?? 3e4,
61
38
  retries: options?.retries ?? 3,
62
- idempotencyEnabled: options?.idempotencyEnabled ?? true
39
+ idempotencyEnabled: options?.idempotencyEnabled ?? true,
40
+ debug: options?.debug ?? false
63
41
  };
64
42
  }
65
43
  /**
@@ -171,28 +149,6 @@ var ConfigLoader = class {
171
149
  }
172
150
  }
173
151
  };
174
- function parseError(response, body) {
175
- const statusCode = response.status;
176
- const errorMessage = body?.error || body?.message || response.statusText || "Unknown error";
177
- const errorCode = body?.code;
178
- const details = body?.details;
179
- switch (statusCode) {
180
- case 401:
181
- case 403:
182
- return new AuthenticationError(errorMessage);
183
- case 400:
184
- return new ValidationError(errorMessage, details);
185
- case 429:
186
- return new RateLimitError(errorMessage);
187
- case 500:
188
- case 502:
189
- case 503:
190
- case 504:
191
- return new NetworkError(errorMessage);
192
- default:
193
- return new ZendFiError(errorMessage, statusCode, errorCode, details);
194
- }
195
- }
196
152
  function generateIdempotencyKey() {
197
153
  const timestamp = Date.now();
198
154
  const random = Math.random().toString(36).substring(2, 15);
@@ -201,271 +157,1513 @@ function generateIdempotencyKey() {
201
157
  function sleep(ms) {
202
158
  return new Promise((resolve) => setTimeout(resolve, ms));
203
159
  }
204
-
205
- // src/client.ts
206
- var ZendFiClient = class {
207
- config;
208
- constructor(options) {
209
- this.config = ConfigLoader.load(options);
210
- ConfigLoader.validateApiKey(this.config.apiKey);
211
- if (this.config.environment === "development") {
212
- console.log(
213
- `\u2713 ZendFi SDK initialized in ${this.config.mode} mode (${this.config.mode === "test" ? "devnet" : "mainnet"})`
214
- );
215
- }
160
+ var RateLimiter = class {
161
+ requests = [];
162
+ maxRequests;
163
+ windowMs;
164
+ constructor(options = {}) {
165
+ this.maxRequests = options.maxRequests ?? 100;
166
+ this.windowMs = options.windowMs ?? 6e4;
216
167
  }
217
168
  /**
218
- * Create a new payment
169
+ * Check if a request can be made without exceeding rate limit
219
170
  */
220
- async createPayment(request) {
221
- return this.request("POST", "/api/v1/payments", {
222
- ...request,
223
- currency: request.currency || "USD",
224
- token: request.token || "USDC"
225
- });
171
+ canMakeRequest() {
172
+ this.pruneOldRequests();
173
+ return this.requests.length < this.maxRequests;
226
174
  }
227
175
  /**
228
- * Get payment by ID
176
+ * Record a request timestamp
229
177
  */
230
- async getPayment(paymentId) {
231
- return this.request("GET", `/api/v1/payments/${paymentId}`);
178
+ recordRequest() {
179
+ this.requests.push(Date.now());
232
180
  }
233
181
  /**
234
- * List all payments with pagination
182
+ * Get remaining requests in current window
235
183
  */
236
- async listPayments(request) {
237
- const params = new URLSearchParams();
238
- if (request?.page) params.append("page", request.page.toString());
239
- if (request?.limit) params.append("limit", request.limit.toString());
240
- if (request?.status) params.append("status", request.status);
241
- if (request?.from_date) params.append("from_date", request.from_date);
242
- if (request?.to_date) params.append("to_date", request.to_date);
243
- const query = params.toString() ? `?${params.toString()}` : "";
244
- return this.request("GET", `/api/v1/payments${query}`);
184
+ getRemainingRequests() {
185
+ this.pruneOldRequests();
186
+ return Math.max(0, this.maxRequests - this.requests.length);
245
187
  }
246
188
  /**
247
- * Create a subscription plan
189
+ * Get time in ms until the rate limit window resets
248
190
  */
249
- async createSubscriptionPlan(request) {
250
- return this.request("POST", "/api/v1/subscriptions/plans", {
251
- ...request,
252
- currency: request.currency || "USD",
253
- interval_count: request.interval_count || 1,
254
- trial_days: request.trial_days || 0
255
- });
191
+ getTimeUntilReset() {
192
+ if (this.requests.length === 0) return 0;
193
+ const oldestRequest = Math.min(...this.requests);
194
+ const resetTime = oldestRequest + this.windowMs;
195
+ return Math.max(0, resetTime - Date.now());
256
196
  }
257
197
  /**
258
- * Get subscription plan by ID
198
+ * Get current rate limit status
259
199
  */
260
- async getSubscriptionPlan(planId) {
261
- return this.request("GET", `/api/v1/subscriptions/plans/${planId}`);
200
+ getStatus() {
201
+ this.pruneOldRequests();
202
+ return {
203
+ remaining: this.getRemainingRequests(),
204
+ limit: this.maxRequests,
205
+ resetInMs: this.getTimeUntilReset(),
206
+ isLimited: !this.canMakeRequest()
207
+ };
262
208
  }
263
209
  /**
264
- * Create a subscription
210
+ * Reset the rate limiter (useful for testing)
265
211
  */
266
- async createSubscription(request) {
267
- return this.request("POST", "/api/v1/subscriptions", request);
212
+ reset() {
213
+ this.requests = [];
268
214
  }
269
- /**
270
- * Get subscription by ID
271
- */
272
- async getSubscription(subscriptionId) {
273
- return this.request("GET", `/api/v1/subscriptions/${subscriptionId}`);
215
+ pruneOldRequests() {
216
+ const cutoff = Date.now() - this.windowMs;
217
+ this.requests = this.requests.filter((t) => t > cutoff);
274
218
  }
275
- /**
276
- * Cancel a subscription
277
- */
278
- async cancelSubscription(subscriptionId) {
279
- return this.request(
280
- "POST",
281
- `/api/v1/subscriptions/${subscriptionId}/cancel`
282
- );
219
+ };
220
+
221
+ // src/errors.ts
222
+ var ZendFiError2 = class _ZendFiError extends Error {
223
+ code;
224
+ type;
225
+ suggestion;
226
+ docs_url;
227
+ statusCode;
228
+ response;
229
+ constructor(data) {
230
+ super(data.message);
231
+ this.name = "ZendFiError";
232
+ this.code = data.code;
233
+ this.type = data.type;
234
+ this.suggestion = data.suggestion;
235
+ this.statusCode = data.statusCode;
236
+ this.response = data.response;
237
+ this.docs_url = `https://docs.zendfi.com/errors/${data.code}`;
238
+ if (Error.captureStackTrace) {
239
+ Error.captureStackTrace(this, _ZendFiError);
240
+ }
283
241
  }
284
242
  /**
285
- * Create a payment link (shareable checkout URL)
243
+ * Format error for display
286
244
  */
287
- async createPaymentLink(request) {
288
- const response = await this.request("POST", "/api/v1/payment-links", {
289
- ...request,
290
- currency: request.currency || "USD",
291
- token: request.token || "USDC"
292
- });
293
- return {
294
- ...response,
295
- url: response.hosted_page_url
296
- };
245
+ toString() {
246
+ let message = `[${this.code}] ${this.message}`;
247
+ if (this.suggestion) {
248
+ message += `
249
+ \u{1F4A1} Suggestion: ${this.suggestion}`;
250
+ }
251
+ message += `
252
+ \u{1F4DA} Docs: ${this.docs_url}`;
253
+ return message;
297
254
  }
298
255
  /**
299
- * Get payment link by link code
256
+ * Convert error to JSON
300
257
  */
301
- async getPaymentLink(linkCode) {
302
- const response = await this.request("GET", `/api/v1/payment-links/${linkCode}`);
258
+ toJSON() {
303
259
  return {
304
- ...response,
305
- url: response.hosted_page_url
260
+ name: this.name,
261
+ code: this.code,
262
+ type: this.type,
263
+ message: this.message,
264
+ suggestion: this.suggestion,
265
+ docs_url: this.docs_url,
266
+ statusCode: this.statusCode
306
267
  };
307
268
  }
308
- /**
309
- * List all payment links for the authenticated merchant
310
- */
311
- async listPaymentLinks() {
312
- const response = await this.request("GET", "/api/v1/payment-links");
313
- return response.map((link) => ({
314
- ...link,
315
- url: link.hosted_page_url
316
- }));
269
+ };
270
+ var AuthenticationError2 = class extends ZendFiError2 {
271
+ constructor(message, code = "authentication_failed", suggestion) {
272
+ super({
273
+ code,
274
+ message,
275
+ type: "authentication_error",
276
+ suggestion: suggestion || "Check your API key in the dashboard at https://app.zendfi.com/settings/api-keys",
277
+ statusCode: 401
278
+ });
279
+ this.name = "AuthenticationError";
317
280
  }
318
- /**
319
- * Create an installment plan
320
- * Split a purchase into multiple scheduled payments
321
- */
322
- async createInstallmentPlan(request) {
323
- const response = await this.request(
324
- "POST",
325
- "/api/v1/installment-plans",
326
- request
327
- );
328
- return {
329
- id: response.plan_id,
330
- plan_id: response.plan_id,
331
- status: response.status
332
- };
281
+ };
282
+ var PaymentError = class extends ZendFiError2 {
283
+ constructor(message, code = "payment_failed", suggestion) {
284
+ super({
285
+ code,
286
+ message,
287
+ type: "payment_error",
288
+ suggestion,
289
+ statusCode: 400
290
+ });
291
+ this.name = "PaymentError";
333
292
  }
334
- /**
335
- * Get installment plan by ID
336
- */
337
- async getInstallmentPlan(planId) {
338
- return this.request("GET", `/api/v1/installment-plans/${planId}`);
293
+ };
294
+ var ValidationError2 = class extends ZendFiError2 {
295
+ constructor(message, code = "validation_failed", suggestion) {
296
+ super({
297
+ code,
298
+ message,
299
+ type: "validation_error",
300
+ suggestion,
301
+ statusCode: 400
302
+ });
303
+ this.name = "ValidationError";
339
304
  }
340
- /**
341
- * List all installment plans for merchant
342
- */
343
- async listInstallmentPlans(params) {
344
- const query = new URLSearchParams();
345
- if (params?.limit) query.append("limit", params.limit.toString());
346
- if (params?.offset) query.append("offset", params.offset.toString());
347
- const queryString = query.toString() ? `?${query.toString()}` : "";
348
- return this.request("GET", `/api/v1/installment-plans${queryString}`);
305
+ };
306
+ var NetworkError2 = class extends ZendFiError2 {
307
+ constructor(message, code = "network_error", suggestion) {
308
+ super({
309
+ code,
310
+ message,
311
+ type: "network_error",
312
+ suggestion: suggestion || "Check your internet connection and try again",
313
+ statusCode: 0
314
+ });
315
+ this.name = "NetworkError";
349
316
  }
350
- /**
351
- * List installment plans for a specific customer
352
- */
353
- async listCustomerInstallmentPlans(customerWallet) {
354
- return this.request(
355
- "GET",
356
- `/api/v1/customers/${customerWallet}/installment-plans`
357
- );
317
+ };
318
+ var RateLimitError2 = class extends ZendFiError2 {
319
+ constructor(message, retryAfter) {
320
+ super({
321
+ code: "rate_limit_exceeded",
322
+ message,
323
+ type: "rate_limit_error",
324
+ suggestion: retryAfter ? `Wait ${retryAfter} seconds before retrying` : "You are making too many requests. Please slow down.",
325
+ statusCode: 429
326
+ });
327
+ this.name = "RateLimitError";
358
328
  }
359
- /**
360
- * Cancel an installment plan
361
- */
362
- async cancelInstallmentPlan(planId) {
363
- return this.request(
364
- "POST",
365
- `/api/v1/installment-plans/${planId}/cancel`
366
- );
329
+ };
330
+ var ApiError = class extends ZendFiError2 {
331
+ constructor(message, code, statusCode, response) {
332
+ super({
333
+ code,
334
+ message,
335
+ type: "api_error",
336
+ statusCode,
337
+ response
338
+ });
339
+ this.name = "ApiError";
367
340
  }
368
- /**
369
- * Create an escrow transaction
370
- * Hold funds until conditions are met
371
- */
372
- async createEscrow(request) {
373
- return this.request("POST", "/api/v1/escrows", {
374
- ...request,
375
- currency: request.currency || "USD",
376
- token: request.token || "USDC"
341
+ };
342
+ var WebhookError = class extends ZendFiError2 {
343
+ constructor(message, code = "webhook_verification_failed", suggestion) {
344
+ super({
345
+ code,
346
+ message,
347
+ type: "webhook_error",
348
+ suggestion: suggestion || "Check your webhook secret matches the one in your dashboard",
349
+ statusCode: 400
377
350
  });
351
+ this.name = "WebhookError";
378
352
  }
379
- /**
380
- * Get escrow by ID
381
- */
382
- async getEscrow(escrowId) {
383
- return this.request("GET", `/api/v1/escrows/${escrowId}`);
353
+ };
354
+ function createZendFiError(statusCode, responseBody, message) {
355
+ const errorMessage = message || responseBody?.error?.message || responseBody?.message || "An error occurred";
356
+ const errorCode = responseBody?.error?.code || responseBody?.code || "unknown_error";
357
+ if (statusCode === 401) {
358
+ return new AuthenticationError2(
359
+ errorMessage,
360
+ errorCode,
361
+ "Verify your API key is correct and not expired"
362
+ );
384
363
  }
385
- /**
386
- * List all escrows for merchant
387
- */
388
- async listEscrows(params) {
389
- const query = new URLSearchParams();
390
- if (params?.limit) query.append("limit", params.limit.toString());
391
- if (params?.offset) query.append("offset", params.offset.toString());
392
- const queryString = query.toString() ? `?${query.toString()}` : "";
393
- return this.request("GET", `/api/v1/escrows${queryString}`);
364
+ if (statusCode === 429) {
365
+ const retryAfter = responseBody?.retry_after;
366
+ return new RateLimitError2(errorMessage, retryAfter);
394
367
  }
395
- /**
396
- * Approve escrow release to seller
397
- */
398
- async approveEscrow(escrowId, request) {
399
- return this.request(
400
- "POST",
401
- `/api/v1/escrows/${escrowId}/approve`,
402
- request
368
+ if (statusCode === 400 || statusCode === 422) {
369
+ return new ValidationError2(errorMessage, errorCode);
370
+ }
371
+ if (statusCode === 402) {
372
+ return new PaymentError(errorMessage, errorCode);
373
+ }
374
+ if (statusCode === 0 || statusCode >= 500) {
375
+ return new NetworkError2(
376
+ errorMessage,
377
+ errorCode,
378
+ statusCode >= 500 ? "The ZendFi API is experiencing issues. Please try again later." : void 0
403
379
  );
404
380
  }
381
+ return new ApiError(errorMessage, errorCode, statusCode, responseBody);
382
+ }
383
+ var ERROR_CODES = {
384
+ // Authentication
385
+ INVALID_API_KEY: "invalid_api_key",
386
+ API_KEY_EXPIRED: "api_key_expired",
387
+ API_KEY_REVOKED: "api_key_revoked",
388
+ // Payment
389
+ INSUFFICIENT_BALANCE: "insufficient_balance",
390
+ PAYMENT_DECLINED: "payment_declined",
391
+ PAYMENT_EXPIRED: "payment_expired",
392
+ INVALID_AMOUNT: "invalid_amount",
393
+ INVALID_CURRENCY: "invalid_currency",
394
+ // Validation
395
+ MISSING_REQUIRED_FIELD: "missing_required_field",
396
+ INVALID_PARAMETER: "invalid_parameter",
397
+ // Network
398
+ NETWORK_ERROR: "network_error",
399
+ TIMEOUT: "timeout",
400
+ // Rate limiting
401
+ RATE_LIMIT_EXCEEDED: "rate_limit_exceeded",
402
+ // Webhook
403
+ WEBHOOK_SIGNATURE_INVALID: "webhook_signature_invalid",
404
+ WEBHOOK_TIMESTAMP_TOO_OLD: "webhook_timestamp_too_old"
405
+ };
406
+ function isZendFiError(error) {
407
+ return error instanceof ZendFiError2;
408
+ }
409
+
410
+ // src/interceptors.ts
411
+ var InterceptorManager = class {
412
+ handlers = [];
405
413
  /**
406
- * Refund escrow to buyer
414
+ * Add an interceptor
407
415
  */
408
- async refundEscrow(escrowId, request) {
409
- return this.request("POST", `/api/v1/escrows/${escrowId}/refund`, request);
416
+ use(handler) {
417
+ this.handlers.push(handler);
418
+ return this.handlers.length - 1;
410
419
  }
411
420
  /**
412
- * Raise a dispute for an escrow
421
+ * Remove an interceptor
413
422
  */
414
- async disputeEscrow(escrowId, request) {
415
- return this.request("POST", `/api/v1/escrows/${escrowId}/dispute`, request);
423
+ eject(id) {
424
+ if (this.handlers[id]) {
425
+ this.handlers[id] = null;
426
+ }
416
427
  }
417
428
  /**
418
- * Create an invoice
429
+ * Execute all interceptors in sequence
419
430
  */
420
- async createInvoice(request) {
421
- return this.request("POST", "/api/v1/invoices", {
422
- ...request,
423
- token: request.token || "USDC"
424
- });
431
+ async execute(initialValue) {
432
+ let result = initialValue;
433
+ for (const handler of this.handlers) {
434
+ if (handler !== null) {
435
+ result = await handler(result);
436
+ }
437
+ }
438
+ return result;
425
439
  }
426
440
  /**
427
- * Get invoice by ID
441
+ * Check if any interceptors are registered
428
442
  */
429
- async getInvoice(invoiceId) {
430
- return this.request("GET", `/api/v1/invoices/${invoiceId}`);
443
+ has() {
444
+ return this.handlers.some((h) => h !== null);
431
445
  }
432
446
  /**
433
- * List all invoices for merchant
447
+ * Clear all interceptors
434
448
  */
435
- async listInvoices() {
436
- return this.request("GET", "/api/v1/invoices");
449
+ clear() {
450
+ this.handlers = [];
437
451
  }
438
- /**
439
- * Send invoice to customer via email
440
- */
441
- async sendInvoice(invoiceId) {
442
- return this.request("POST", `/api/v1/invoices/${invoiceId}/send`);
452
+ };
453
+ function createInterceptors() {
454
+ return {
455
+ request: new InterceptorManager(),
456
+ response: new InterceptorManager(),
457
+ error: new InterceptorManager()
458
+ };
459
+ }
460
+
461
+ // src/api/agent.ts
462
+ function normalizeArrayResponse(response, key) {
463
+ if (Array.isArray(response)) {
464
+ return response;
465
+ }
466
+ return response[key] || [];
467
+ }
468
+ var AgentAPI = class {
469
+ constructor(request) {
470
+ this.request = request;
443
471
  }
472
+ // ============================================
473
+ // Agent API Keys
474
+ // ============================================
444
475
  /**
445
- * Verify webhook signature using HMAC-SHA256
476
+ * Create a new agent API key with scoped permissions
446
477
  *
447
- * @param request - Webhook verification request containing payload, signature, and secret
448
- * @returns true if signature is valid, false otherwise
478
+ * Agent keys (prefixed with `zai_`) have limited permissions compared to
479
+ * merchant keys. This enables safe delegation to AI agents.
480
+ *
481
+ * @param request - Agent key configuration
482
+ * @returns The created agent key (full_key only returned on creation!)
449
483
  *
450
484
  * @example
451
485
  * ```typescript
452
- * const isValid = zendfi.verifyWebhook({
453
- * payload: req.body,
454
- * signature: req.headers['x-zendfi-signature'],
455
- * secret: process.env.ZENDFI_WEBHOOK_SECRET
486
+ * const agentKey = await zendfi.agent.createKey({
487
+ * name: 'Shopping Assistant',
488
+ * agent_id: 'shopping-assistant-v1',
489
+ * scopes: ['create_payments'],
490
+ * rate_limit_per_hour: 500,
456
491
  * });
457
492
  *
458
- * if (!isValid) {
459
- * return res.status(401).json({ error: 'Invalid signature' });
460
- * }
493
+ * // IMPORTANT: Save the full_key now - it won't be shown again!
494
+ * console.log(agentKey.full_key); // => "zai_test_abc123..."
461
495
  * ```
462
496
  */
463
- verifyWebhook(request) {
464
- try {
465
- if (!request.payload || !request.signature || !request.secret) {
466
- return false;
467
- }
468
- let payloadString;
497
+ async createKey(request) {
498
+ return this.request("POST", "/api/v1/agent-keys", {
499
+ name: request.name,
500
+ agent_id: request.agent_id,
501
+ agent_name: request.agent_name,
502
+ scopes: request.scopes || ["create_payments"],
503
+ rate_limit_per_hour: request.rate_limit_per_hour || 1e3,
504
+ metadata: request.metadata
505
+ });
506
+ }
507
+ /**
508
+ * List all agent API keys for the merchant
509
+ *
510
+ * @returns Array of agent API keys (without full_key for security)
511
+ *
512
+ * @example
513
+ * ```typescript
514
+ * const keys = await zendfi.agent.listKeys();
515
+ * keys.forEach(key => {
516
+ * console.log(`${key.name}: ${key.key_prefix}*** (${key.scopes.join(', ')})`);
517
+ * });
518
+ * ```
519
+ */
520
+ async listKeys() {
521
+ const response = await this.request(
522
+ "GET",
523
+ "/api/v1/agent-keys"
524
+ );
525
+ return normalizeArrayResponse(response, "keys");
526
+ }
527
+ /**
528
+ * Revoke an agent API key
529
+ *
530
+ * Once revoked, the key cannot be used for any API calls.
531
+ * This action is irreversible.
532
+ *
533
+ * @param keyId - UUID of the agent key to revoke
534
+ *
535
+ * @example
536
+ * ```typescript
537
+ * await zendfi.agent.revokeKey('ak_123...');
538
+ * console.log('Agent key revoked');
539
+ * ```
540
+ */
541
+ async revokeKey(keyId) {
542
+ await this.request("POST", `/api/v1/agent-keys/${keyId}/revoke`);
543
+ }
544
+ // ============================================
545
+ // Agent Sessions
546
+ // ============================================
547
+ /**
548
+ * Create an agent session with spending limits
549
+ *
550
+ * Sessions provide time-bounded authorization for agents to make payments
551
+ * on behalf of users, with configurable spending limits.
552
+ *
553
+ * @param request - Session configuration
554
+ * @returns The created session with token
555
+ *
556
+ * @example
557
+ * ```typescript
558
+ * const session = await zendfi.agent.createSession({
559
+ * agent_id: 'shopping-assistant-v1',
560
+ * agent_name: 'Shopping Assistant',
561
+ * user_wallet: 'Hx7B...abc',
562
+ * limits: {
563
+ * max_per_transaction: 100,
564
+ * max_per_day: 500,
565
+ * require_approval_above: 50,
566
+ * },
567
+ * duration_hours: 24,
568
+ * });
569
+ *
570
+ * // Use session_token for subsequent API calls
571
+ * console.log(session.session_token); // => "zai_session_..."
572
+ * ```
573
+ */
574
+ async createSession(request) {
575
+ return this.request("POST", "/api/v1/ai/sessions", {
576
+ agent_id: request.agent_id,
577
+ agent_name: request.agent_name,
578
+ user_wallet: request.user_wallet,
579
+ limits: request.limits || {
580
+ max_per_transaction: 1e3,
581
+ max_per_day: 5e3,
582
+ max_per_week: 2e4,
583
+ max_per_month: 5e4,
584
+ require_approval_above: 500
585
+ },
586
+ allowed_merchants: request.allowed_merchants,
587
+ duration_hours: request.duration_hours || 24,
588
+ mint_pkp: request.mint_pkp,
589
+ metadata: request.metadata
590
+ });
591
+ }
592
+ /**
593
+ * List all agent sessions
594
+ *
595
+ * @returns Array of agent sessions (both active and expired)
596
+ *
597
+ * @example
598
+ * ```typescript
599
+ * const sessions = await zendfi.agent.listSessions();
600
+ * const activeSessions = sessions.filter(s => s.is_active);
601
+ * console.log(`${activeSessions.length} active sessions`);
602
+ * ```
603
+ */
604
+ async listSessions() {
605
+ const response = await this.request(
606
+ "GET",
607
+ "/api/v1/ai/sessions"
608
+ );
609
+ return normalizeArrayResponse(response, "sessions");
610
+ }
611
+ /**
612
+ * Get a specific agent session by ID
613
+ *
614
+ * @param sessionId - UUID of the session
615
+ * @returns The session details with remaining limits
616
+ *
617
+ * @example
618
+ * ```typescript
619
+ * const session = await zendfi.agent.getSession('sess_123...');
620
+ * console.log(`Remaining today: $${session.remaining_today}`);
621
+ * console.log(`Expires: ${session.expires_at}`);
622
+ * ```
623
+ */
624
+ async getSession(sessionId) {
625
+ return this.request("GET", `/api/v1/ai/sessions/${sessionId}`);
626
+ }
627
+ /**
628
+ * Revoke an agent session
629
+ *
630
+ * Immediately invalidates the session, preventing any further payments.
631
+ * This action is irreversible.
632
+ *
633
+ * @param sessionId - UUID of the session to revoke
634
+ *
635
+ * @example
636
+ * ```typescript
637
+ * await zendfi.agent.revokeSession('sess_123...');
638
+ * console.log('Session revoked - agent can no longer make payments');
639
+ * ```
640
+ */
641
+ async revokeSession(sessionId) {
642
+ await this.request("POST", `/api/v1/ai/sessions/${sessionId}/revoke`);
643
+ }
644
+ // ============================================
645
+ // Agent Analytics
646
+ // ============================================
647
+ /**
648
+ * Get analytics for all agent activity
649
+ *
650
+ * @returns Comprehensive analytics including payments, success rate, and PPP savings
651
+ *
652
+ * @example
653
+ * ```typescript
654
+ * const analytics = await zendfi.agent.getAnalytics();
655
+ * console.log(`Total volume: $${analytics.total_volume_usd}`);
656
+ * console.log(`Success rate: ${(analytics.success_rate * 100).toFixed(1)}%`);
657
+ * console.log(`PPP savings: $${analytics.ppp_savings_usd}`);
658
+ * ```
659
+ */
660
+ async getAnalytics() {
661
+ return this.request("GET", "/api/v1/analytics/agents");
662
+ }
663
+ };
664
+
665
+ // src/api/intents.ts
666
+ var PaymentIntentsAPI = class {
667
+ constructor(request) {
668
+ this.request = request;
669
+ }
670
+ /**
671
+ * Create a payment intent
672
+ *
673
+ * This is step 1 of the two-phase payment flow. The intent reserves
674
+ * the payment amount and provides a client_secret for confirmation.
675
+ *
676
+ * @param request - Payment intent configuration
677
+ * @returns The created payment intent with client_secret
678
+ *
679
+ * @example
680
+ * ```typescript
681
+ * const intent = await zendfi.intents.create({
682
+ * amount: 49.99,
683
+ * description: 'Pro Plan - Monthly',
684
+ * capture_method: 'automatic', // or 'manual' for auth-only
685
+ * expires_in_seconds: 3600, // 1 hour
686
+ * });
687
+ *
688
+ * // Store intent.id and pass intent.client_secret to frontend
689
+ * console.log(`Intent created: ${intent.id}`);
690
+ * console.log(`Status: ${intent.status}`); // "requires_payment"
691
+ * ```
692
+ */
693
+ async create(request) {
694
+ return this.request("POST", "/api/v1/payment-intents", {
695
+ amount: request.amount,
696
+ currency: request.currency || "USD",
697
+ description: request.description,
698
+ capture_method: request.capture_method || "automatic",
699
+ agent_id: request.agent_id,
700
+ agent_name: request.agent_name,
701
+ metadata: request.metadata,
702
+ expires_in_seconds: request.expires_in_seconds || 86400
703
+ // 24h default
704
+ });
705
+ }
706
+ /**
707
+ * Get a payment intent by ID
708
+ *
709
+ * @param intentId - UUID of the payment intent
710
+ * @returns The payment intent details
711
+ *
712
+ * @example
713
+ * ```typescript
714
+ * const intent = await zendfi.intents.get('pi_123...');
715
+ * console.log(`Status: ${intent.status}`);
716
+ * if (intent.payment_id) {
717
+ * console.log(`Payment: ${intent.payment_id}`);
718
+ * }
719
+ * ```
720
+ */
721
+ async get(intentId) {
722
+ return this.request("GET", `/api/v1/payment-intents/${intentId}`);
723
+ }
724
+ /**
725
+ * List payment intents
726
+ *
727
+ * @param options - Filter and pagination options
728
+ * @returns Array of payment intents
729
+ *
730
+ * @example
731
+ * ```typescript
732
+ * // Get recent pending intents
733
+ * const intents = await zendfi.intents.list({
734
+ * status: 'requires_payment',
735
+ * limit: 20,
736
+ * });
737
+ * ```
738
+ */
739
+ async list(options) {
740
+ const params = new URLSearchParams();
741
+ if (options?.status) params.append("status", options.status);
742
+ if (options?.limit) params.append("limit", options.limit.toString());
743
+ if (options?.offset) params.append("offset", options.offset.toString());
744
+ const query = params.toString() ? `?${params.toString()}` : "";
745
+ const response = await this.request(
746
+ "GET",
747
+ `/api/v1/payment-intents${query}`
748
+ );
749
+ return Array.isArray(response) ? response : response.intents;
750
+ }
751
+ /**
752
+ * Confirm a payment intent
753
+ *
754
+ * This is step 2 of the two-phase payment flow. Confirmation triggers
755
+ * the actual payment using the customer's wallet.
756
+ *
757
+ * @param intentId - UUID of the payment intent
758
+ * @param request - Confirmation details including customer wallet
759
+ * @returns The confirmed payment intent with payment_id
760
+ *
761
+ * @example
762
+ * ```typescript
763
+ * const confirmed = await zendfi.intents.confirm('pi_123...', {
764
+ * client_secret: 'pi_secret_abc...',
765
+ * customer_wallet: 'Hx7B...abc',
766
+ * auto_gasless: true,
767
+ * });
768
+ *
769
+ * if (confirmed.status === 'succeeded') {
770
+ * console.log(`Payment complete: ${confirmed.payment_id}`);
771
+ * }
772
+ * ```
773
+ */
774
+ async confirm(intentId, request) {
775
+ return this.request("POST", `/api/v1/payment-intents/${intentId}/confirm`, {
776
+ client_secret: request.client_secret,
777
+ customer_wallet: request.customer_wallet,
778
+ payment_type: request.payment_type,
779
+ auto_gasless: request.auto_gasless,
780
+ metadata: request.metadata
781
+ });
782
+ }
783
+ /**
784
+ * Cancel a payment intent
785
+ *
786
+ * Canceling releases any hold on the payment amount. Cannot cancel
787
+ * intents that are already processing or succeeded.
788
+ *
789
+ * @param intentId - UUID of the payment intent
790
+ * @returns The canceled payment intent
791
+ *
792
+ * @example
793
+ * ```typescript
794
+ * const canceled = await zendfi.intents.cancel('pi_123...');
795
+ * console.log(`Status: ${canceled.status}`); // "canceled"
796
+ * ```
797
+ */
798
+ async cancel(intentId) {
799
+ return this.request("POST", `/api/v1/payment-intents/${intentId}/cancel`);
800
+ }
801
+ /**
802
+ * Get events for a payment intent
803
+ *
804
+ * Events track the full lifecycle of the intent, including creation,
805
+ * confirmation attempts, and status changes.
806
+ *
807
+ * @param intentId - UUID of the payment intent
808
+ * @returns Array of events in chronological order
809
+ *
810
+ * @example
811
+ * ```typescript
812
+ * const events = await zendfi.intents.getEvents('pi_123...');
813
+ * events.forEach(event => {
814
+ * console.log(`${event.created_at}: ${event.event_type}`);
815
+ * });
816
+ * ```
817
+ */
818
+ async getEvents(intentId) {
819
+ const response = await this.request(
820
+ "GET",
821
+ `/api/v1/payment-intents/${intentId}/events`
822
+ );
823
+ return Array.isArray(response) ? response : response.events;
824
+ }
825
+ };
826
+
827
+ // src/api/pricing.ts
828
+ var PricingAPI = class {
829
+ constructor(request) {
830
+ this.request = request;
831
+ }
832
+ /**
833
+ * Get PPP factor for a specific country
834
+ *
835
+ * Returns the purchasing power parity adjustment factor for the given
836
+ * country code. Use this to calculate localized pricing.
837
+ *
838
+ * @param countryCode - ISO 3166-1 alpha-2 country code (e.g., "BR", "IN", "NG")
839
+ * @returns PPP factor and suggested adjustment
840
+ *
841
+ * @example
842
+ * ```typescript
843
+ * const factor = await zendfi.pricing.getPPPFactor('BR');
844
+ * // {
845
+ * // country_code: 'BR',
846
+ * // country_name: 'Brazil',
847
+ * // ppp_factor: 0.35,
848
+ * // currency_code: 'BRL',
849
+ * // adjustment_percentage: 35.0
850
+ * // }
851
+ *
852
+ * // Calculate localized price
853
+ * const usdPrice = 100;
854
+ * const localPrice = usdPrice * (1 - factor.adjustment_percentage / 100);
855
+ * console.log(`$${localPrice} for Brazilian customers`);
856
+ * ```
857
+ */
858
+ async getPPPFactor(countryCode) {
859
+ return this.request("POST", "/api/v1/ai/pricing/ppp-factor", {
860
+ country_code: countryCode.toUpperCase()
861
+ });
862
+ }
863
+ /**
864
+ * List all available PPP factors
865
+ *
866
+ * Returns PPP factors for all supported countries. Useful for building
867
+ * pricing tables or pre-computing regional prices.
868
+ *
869
+ * @returns Array of PPP factors for all supported countries
870
+ *
871
+ * @example
872
+ * ```typescript
873
+ * const factors = await zendfi.pricing.listFactors();
874
+ *
875
+ * // Create pricing tiers
876
+ * const tiers = factors.map(f => ({
877
+ * country: f.country_name,
878
+ * price: (100 * f.ppp_factor).toFixed(2),
879
+ * }));
880
+ *
881
+ * console.table(tiers);
882
+ * ```
883
+ */
884
+ async listFactors() {
885
+ const response = await this.request(
886
+ "GET",
887
+ "/api/v1/ai/pricing/ppp-factors"
888
+ );
889
+ return Array.isArray(response) ? response : response.factors;
890
+ }
891
+ /**
892
+ * Get AI-powered pricing suggestion
893
+ *
894
+ * Returns an intelligent pricing recommendation based on the user's
895
+ * location, wallet history, and your pricing configuration.
896
+ *
897
+ * @param request - Pricing suggestion request with user context
898
+ * @returns AI-generated pricing suggestion with reasoning
899
+ *
900
+ * @example
901
+ * ```typescript
902
+ * const suggestion = await zendfi.pricing.getSuggestion({
903
+ * agent_id: 'shopping-assistant',
904
+ * base_price: 99.99,
905
+ * user_profile: {
906
+ * location_country: 'BR',
907
+ * context: 'first-time',
908
+ * },
909
+ * ppp_config: {
910
+ * enabled: true,
911
+ * max_discount_percent: 50,
912
+ * floor_price: 29.99,
913
+ * },
914
+ * });
915
+ *
916
+ * console.log(`Suggested: $${suggestion.suggested_amount}`);
917
+ * console.log(`Reason: ${suggestion.reasoning}`);
918
+ * // => "Price adjusted for Brazilian purchasing power (35% PPP discount)
919
+ * // plus 10% first-time customer discount"
920
+ * ```
921
+ */
922
+ async getSuggestion(request) {
923
+ return this.request("POST", "/api/v1/ai/pricing/suggest", {
924
+ agent_id: request.agent_id,
925
+ product_id: request.product_id,
926
+ base_price: request.base_price,
927
+ currency: request.currency || "USD",
928
+ user_profile: request.user_profile,
929
+ ppp_config: request.ppp_config
930
+ });
931
+ }
932
+ /**
933
+ * Calculate localized price for a given base price and country
934
+ *
935
+ * Convenience method that combines getPPPFactor with price calculation.
936
+ *
937
+ * @param basePrice - Original price in USD
938
+ * @param countryCode - ISO 3166-1 alpha-2 country code
939
+ * @returns Object with original and adjusted prices
940
+ *
941
+ * @example
942
+ * ```typescript
943
+ * const result = await zendfi.pricing.calculateLocalPrice(100, 'IN');
944
+ * console.log(`Original: $${result.original}`);
945
+ * console.log(`Local: $${result.adjusted}`);
946
+ * console.log(`Savings: $${result.savings} (${result.discount_percentage}%)`);
947
+ * ```
948
+ */
949
+ async calculateLocalPrice(basePrice, countryCode) {
950
+ const factor = await this.getPPPFactor(countryCode);
951
+ const adjusted = Number((basePrice * factor.ppp_factor).toFixed(2));
952
+ const savings = Number((basePrice - adjusted).toFixed(2));
953
+ return {
954
+ original: basePrice,
955
+ adjusted,
956
+ savings,
957
+ discount_percentage: factor.adjustment_percentage,
958
+ country: factor.country_name,
959
+ ppp_factor: factor.ppp_factor
960
+ };
961
+ }
962
+ };
963
+
964
+ // src/api/autonomy.ts
965
+ var AutonomyAPI = class {
966
+ constructor(request) {
967
+ this.request = request;
968
+ }
969
+ /**
970
+ * Enable autonomous signing for a session key
971
+ *
972
+ * This grants an AI agent the ability to sign transactions on behalf of
973
+ * the user, up to the specified spending limit and duration.
974
+ *
975
+ * **Prerequisites:**
976
+ * 1. Create a device-bound session key first
977
+ * 2. Generate a delegation signature (see `createDelegationMessage`)
978
+ * 3. Optionally encrypt keypair with Lit Protocol for true autonomy
979
+ *
980
+ * @param sessionKeyId - UUID of the session key
981
+ * @param request - Autonomy configuration including delegation signature
982
+ * @returns The created autonomous delegate
983
+ *
984
+ * @example
985
+ * ```typescript
986
+ * // The user must sign this exact message format
987
+ * const message = zendfi.autonomy.createDelegationMessage(
988
+ * sessionKeyId, 100, '2024-12-10T00:00:00Z'
989
+ * );
990
+ *
991
+ * // Have user sign with their session key
992
+ * const signature = await signWithSessionKey(message, pin);
993
+ *
994
+ * // Enable autonomous mode
995
+ * const delegate = await zendfi.autonomy.enable(sessionKeyId, {
996
+ * max_amount_usd: 100,
997
+ * duration_hours: 24,
998
+ * delegation_signature: signature,
999
+ * });
1000
+ *
1001
+ * console.log(`Delegate ID: ${delegate.delegate_id}`);
1002
+ * console.log(`Expires: ${delegate.expires_at}`);
1003
+ * ```
1004
+ */
1005
+ async enable(sessionKeyId, request) {
1006
+ return this.request(
1007
+ "POST",
1008
+ `/api/v1/ai/session-keys/${sessionKeyId}/enable-autonomy`,
1009
+ {
1010
+ max_amount_usd: request.max_amount_usd,
1011
+ duration_hours: request.duration_hours,
1012
+ delegation_signature: request.delegation_signature,
1013
+ expires_at: request.expires_at,
1014
+ lit_encrypted_keypair: request.lit_encrypted_keypair,
1015
+ lit_data_hash: request.lit_data_hash,
1016
+ metadata: request.metadata
1017
+ }
1018
+ );
1019
+ }
1020
+ /**
1021
+ * Revoke autonomous mode for a session key
1022
+ *
1023
+ * Immediately invalidates the autonomous delegate, preventing any further
1024
+ * automatic payments. The session key itself remains valid for manual use.
1025
+ *
1026
+ * @param sessionKeyId - UUID of the session key
1027
+ * @param reason - Optional reason for revocation (logged for audit)
1028
+ *
1029
+ * @example
1030
+ * ```typescript
1031
+ * await zendfi.autonomy.revoke('sk_123...', 'User requested revocation');
1032
+ * console.log('Autonomous mode disabled');
1033
+ * ```
1034
+ */
1035
+ async revoke(sessionKeyId, reason) {
1036
+ const request = { reason };
1037
+ await this.request(
1038
+ "POST",
1039
+ `/api/v1/ai/session-keys/${sessionKeyId}/revoke-autonomy`,
1040
+ request
1041
+ );
1042
+ }
1043
+ /**
1044
+ * Get autonomy status for a session key
1045
+ *
1046
+ * Returns whether autonomous mode is enabled and details about the
1047
+ * active delegate including remaining spending allowance.
1048
+ *
1049
+ * @param sessionKeyId - UUID of the session key
1050
+ * @returns Autonomy status with delegate details
1051
+ *
1052
+ * @example
1053
+ * ```typescript
1054
+ * const status = await zendfi.autonomy.getStatus('sk_123...');
1055
+ *
1056
+ * if (status.autonomous_mode_enabled && status.delegate) {
1057
+ * console.log(`Remaining: $${status.delegate.remaining_usd}`);
1058
+ * console.log(`Expires: ${status.delegate.expires_at}`);
1059
+ * } else {
1060
+ * console.log('Autonomous mode not enabled');
1061
+ * }
1062
+ * ```
1063
+ */
1064
+ async getStatus(sessionKeyId) {
1065
+ return this.request(
1066
+ "GET",
1067
+ `/api/v1/ai/session-keys/${sessionKeyId}/autonomy-status`
1068
+ );
1069
+ }
1070
+ /**
1071
+ * Create the delegation message that needs to be signed
1072
+ *
1073
+ * This generates the exact message format required for the delegation
1074
+ * signature. The user must sign this message with their session key.
1075
+ *
1076
+ * **Message format:**
1077
+ * ```
1078
+ * I authorize autonomous delegate for session {id} to spend up to ${amount} until {expiry}
1079
+ * ```
1080
+ *
1081
+ * @param sessionKeyId - UUID of the session key
1082
+ * @param maxAmountUsd - Maximum spending amount in USD
1083
+ * @param expiresAt - ISO 8601 expiration timestamp
1084
+ * @returns The message to be signed
1085
+ *
1086
+ * @example
1087
+ * ```typescript
1088
+ * const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
1089
+ * const message = zendfi.autonomy.createDelegationMessage(
1090
+ * 'sk_123...',
1091
+ * 100,
1092
+ * expiresAt
1093
+ * );
1094
+ * // => "I authorize autonomous delegate for session sk_123... to spend up to $100 until 2024-12-06T..."
1095
+ *
1096
+ * // Sign with nacl.sign.detached() or similar
1097
+ * const signature = signMessage(message, keypair);
1098
+ * ```
1099
+ */
1100
+ createDelegationMessage(sessionKeyId, maxAmountUsd, expiresAt) {
1101
+ return `I authorize autonomous delegate for session ${sessionKeyId} to spend up to $${maxAmountUsd} until ${expiresAt}`;
1102
+ }
1103
+ /**
1104
+ * Validate delegation signature parameters
1105
+ *
1106
+ * Helper method to check if autonomy parameters are valid before
1107
+ * making the API call.
1108
+ *
1109
+ * @param request - The enable autonomy request to validate
1110
+ * @throws Error if validation fails
1111
+ *
1112
+ * @example
1113
+ * ```typescript
1114
+ * try {
1115
+ * zendfi.autonomy.validateRequest(request);
1116
+ * const delegate = await zendfi.autonomy.enable(sessionKeyId, request);
1117
+ * } catch (error) {
1118
+ * console.error('Invalid request:', error.message);
1119
+ * }
1120
+ * ```
1121
+ */
1122
+ validateRequest(request) {
1123
+ if (request.max_amount_usd <= 0) {
1124
+ throw new Error("max_amount_usd must be positive");
1125
+ }
1126
+ if (request.duration_hours < 1 || request.duration_hours > 168) {
1127
+ throw new Error("duration_hours must be between 1 and 168 (7 days)");
1128
+ }
1129
+ if (!request.delegation_signature || request.delegation_signature.length === 0) {
1130
+ throw new Error("delegation_signature is required");
1131
+ }
1132
+ const base64Regex = /^[A-Za-z0-9+/]+=*$/;
1133
+ if (!base64Regex.test(request.delegation_signature)) {
1134
+ throw new Error("delegation_signature must be base64 encoded");
1135
+ }
1136
+ }
1137
+ };
1138
+
1139
+ // src/api/smart-payments.ts
1140
+ var SmartPaymentsAPI = class {
1141
+ constructor(request) {
1142
+ this.request = request;
1143
+ }
1144
+ /**
1145
+ * Execute an AI-powered smart payment
1146
+ *
1147
+ * Smart payments analyze the context and automatically apply optimizations:
1148
+ * - **PPP Pricing**: Auto-adjusts based on customer location
1149
+ * - **Gasless**: Detects when user needs gas subsidization
1150
+ * - **Instant Settlement**: Optional immediate merchant payout
1151
+ * - **Escrow**: Optional fund holding for service delivery
1152
+ *
1153
+ * @param request - Smart payment request configuration
1154
+ * @returns Payment result with status and receipt
1155
+ *
1156
+ * @example
1157
+ * ```typescript
1158
+ * // Basic smart payment
1159
+ * const result = await zendfi.payments.smart({
1160
+ * agent_id: 'my-agent',
1161
+ * user_wallet: 'Hx7B...abc',
1162
+ * amount_usd: 99.99,
1163
+ * description: 'Annual Pro Plan',
1164
+ * });
1165
+ *
1166
+ * // With all options
1167
+ * const result = await zendfi.payments.smart({
1168
+ * agent_id: 'my-agent',
1169
+ * session_token: 'zai_session_...', // For limit enforcement
1170
+ * user_wallet: 'Hx7B...abc',
1171
+ * amount_usd: 99.99,
1172
+ * token: 'USDC',
1173
+ * auto_detect_gasless: true,
1174
+ * instant_settlement: true,
1175
+ * enable_escrow: false,
1176
+ * description: 'Annual Pro Plan',
1177
+ * product_details: {
1178
+ * name: 'Pro Plan',
1179
+ * sku: 'PRO-ANNUAL',
1180
+ * },
1181
+ * metadata: {
1182
+ * user_id: 'usr_123',
1183
+ * },
1184
+ * });
1185
+ *
1186
+ * if (result.requires_signature) {
1187
+ * // Device-bound flow: need user to sign
1188
+ * console.log('Please sign:', result.unsigned_transaction);
1189
+ * console.log('Submit to:', result.submit_url);
1190
+ * } else {
1191
+ * // Auto-signed (custodial or autonomous delegate)
1192
+ * console.log('Payment complete:', result.transaction_signature);
1193
+ * }
1194
+ * ```
1195
+ */
1196
+ async execute(request) {
1197
+ return this.request("POST", "/api/v1/ai/smart-payment", {
1198
+ session_token: request.session_token,
1199
+ agent_id: request.agent_id,
1200
+ user_wallet: request.user_wallet,
1201
+ amount_usd: request.amount_usd,
1202
+ merchant_id: request.merchant_id,
1203
+ token: request.token || "USDC",
1204
+ auto_detect_gasless: request.auto_detect_gasless,
1205
+ instant_settlement: request.instant_settlement,
1206
+ enable_escrow: request.enable_escrow,
1207
+ description: request.description,
1208
+ product_details: request.product_details,
1209
+ metadata: request.metadata
1210
+ });
1211
+ }
1212
+ /**
1213
+ * Submit a signed transaction from device-bound flow
1214
+ *
1215
+ * When a smart payment returns `requires_signature: true`, the client
1216
+ * must sign the transaction and submit it here.
1217
+ *
1218
+ * @param paymentId - UUID of the payment
1219
+ * @param signedTransaction - Base64 encoded signed transaction
1220
+ * @returns Updated payment response
1221
+ *
1222
+ * @example
1223
+ * ```typescript
1224
+ * // After user signs the transaction
1225
+ * const result = await zendfi.payments.submitSigned(
1226
+ * payment.payment_id,
1227
+ * signedTransaction
1228
+ * );
1229
+ *
1230
+ * console.log(`Confirmed in ${result.confirmed_in_ms}ms`);
1231
+ * ```
1232
+ */
1233
+ async submitSigned(paymentId, signedTransaction) {
1234
+ return this.request(
1235
+ "POST",
1236
+ `/api/v1/ai/payments/${paymentId}/submit-signed`,
1237
+ {
1238
+ signed_transaction: signedTransaction
1239
+ }
1240
+ );
1241
+ }
1242
+ };
1243
+
1244
+ // src/client.ts
1245
+ var ZendFiClient = class {
1246
+ config;
1247
+ interceptors;
1248
+ // ============================================
1249
+ // Agentic Intent Protocol APIs
1250
+ // ============================================
1251
+ /**
1252
+ * Agent API - Manage agent API keys and sessions
1253
+ *
1254
+ * @example
1255
+ * ```typescript
1256
+ * // Create an agent API key
1257
+ * const agentKey = await zendfi.agent.createKey({
1258
+ * name: 'Shopping Assistant',
1259
+ * agent_id: 'shopping-assistant-v1',
1260
+ * scopes: ['create_payments'],
1261
+ * });
1262
+ *
1263
+ * // Create an agent session
1264
+ * const session = await zendfi.agent.createSession({
1265
+ * agent_id: 'shopping-assistant-v1',
1266
+ * user_wallet: 'Hx7B...abc',
1267
+ * limits: { max_per_day: 500 },
1268
+ * });
1269
+ * ```
1270
+ */
1271
+ agent;
1272
+ /**
1273
+ * Payment Intents API - Two-phase payment flow
1274
+ *
1275
+ * @example
1276
+ * ```typescript
1277
+ * // Create intent
1278
+ * const intent = await zendfi.intents.create({ amount: 99.99 });
1279
+ *
1280
+ * // Confirm when ready
1281
+ * await zendfi.intents.confirm(intent.id, {
1282
+ * client_secret: intent.client_secret,
1283
+ * customer_wallet: 'Hx7B...abc',
1284
+ * });
1285
+ * ```
1286
+ */
1287
+ intents;
1288
+ /**
1289
+ * Pricing API - PPP and AI-powered pricing
1290
+ *
1291
+ * @example
1292
+ * ```typescript
1293
+ * // Get PPP factor for Brazil
1294
+ * const factor = await zendfi.pricing.getPPPFactor('BR');
1295
+ * const localPrice = 100 * factor.ppp_factor; // $35 for Brazil
1296
+ *
1297
+ * // Get AI pricing suggestion
1298
+ * const suggestion = await zendfi.pricing.getSuggestion({
1299
+ * agent_id: 'my-agent',
1300
+ * base_price: 100,
1301
+ * user_profile: { location_country: 'BR' },
1302
+ * });
1303
+ * ```
1304
+ */
1305
+ pricing;
1306
+ /**
1307
+ * Autonomy API - Enable autonomous agent signing
1308
+ *
1309
+ * @example
1310
+ * ```typescript
1311
+ * // Enable autonomous mode for a session key
1312
+ * const delegate = await zendfi.autonomy.enable(sessionKeyId, {
1313
+ * max_amount_usd: 100,
1314
+ * duration_hours: 24,
1315
+ * delegation_signature: signature,
1316
+ * });
1317
+ *
1318
+ * // Check status
1319
+ * const status = await zendfi.autonomy.getStatus(sessionKeyId);
1320
+ * ```
1321
+ */
1322
+ autonomy;
1323
+ /**
1324
+ * Smart Payments API - AI-powered payment routing
1325
+ *
1326
+ * Create intelligent payments that automatically:
1327
+ * - Apply PPP discounts based on user location
1328
+ * - Use agent sessions when available
1329
+ * - Route to optimal payment paths
1330
+ *
1331
+ * @example
1332
+ * ```typescript
1333
+ * const payment = await zendfi.smart.create({
1334
+ * amount_usd: 99.99,
1335
+ * wallet_address: 'Hx7B...abc',
1336
+ * merchant_id: 'merch_123',
1337
+ * country_code: 'BR', // Apply PPP
1338
+ * enable_ppp: true,
1339
+ * });
1340
+ *
1341
+ * console.log(`Original: $${payment.original_amount_usd}`);
1342
+ * console.log(`Final: $${payment.final_amount_usd}`);
1343
+ * // Original: $99.99
1344
+ * // Final: $64.99 (35% PPP discount applied)
1345
+ * ```
1346
+ */
1347
+ smart;
1348
+ constructor(options) {
1349
+ this.config = ConfigLoader.load(options);
1350
+ ConfigLoader.validateApiKey(this.config.apiKey);
1351
+ this.interceptors = createInterceptors();
1352
+ const boundRequest = this.request.bind(this);
1353
+ this.agent = new AgentAPI(boundRequest);
1354
+ this.intents = new PaymentIntentsAPI(boundRequest);
1355
+ this.pricing = new PricingAPI(boundRequest);
1356
+ this.autonomy = new AutonomyAPI(boundRequest);
1357
+ this.smart = new SmartPaymentsAPI(boundRequest);
1358
+ if (this.config.environment === "development" || this.config.debug) {
1359
+ console.log(
1360
+ `\u2713 ZendFi SDK initialized in ${this.config.mode} mode (${this.config.mode === "test" ? "devnet" : "mainnet"})`
1361
+ );
1362
+ if (this.config.debug) {
1363
+ console.log("[ZendFi] Debug mode enabled");
1364
+ }
1365
+ }
1366
+ }
1367
+ /**
1368
+ * Create a new payment
1369
+ */
1370
+ async createPayment(request) {
1371
+ return this.request("POST", "/api/v1/payments", {
1372
+ ...request,
1373
+ currency: request.currency || "USD",
1374
+ token: request.token || "USDC"
1375
+ });
1376
+ }
1377
+ /**
1378
+ * Get payment by ID
1379
+ */
1380
+ async getPayment(paymentId) {
1381
+ return this.request("GET", `/api/v1/payments/${paymentId}`);
1382
+ }
1383
+ /**
1384
+ * List all payments with pagination
1385
+ */
1386
+ async listPayments(request) {
1387
+ const params = new URLSearchParams();
1388
+ if (request?.page) params.append("page", request.page.toString());
1389
+ if (request?.limit) params.append("limit", request.limit.toString());
1390
+ if (request?.status) params.append("status", request.status);
1391
+ if (request?.from_date) params.append("from_date", request.from_date);
1392
+ if (request?.to_date) params.append("to_date", request.to_date);
1393
+ const query = params.toString() ? `?${params.toString()}` : "";
1394
+ return this.request("GET", `/api/v1/payments${query}`);
1395
+ }
1396
+ /**
1397
+ * Create a subscription plan
1398
+ */
1399
+ async createSubscriptionPlan(request) {
1400
+ return this.request("POST", "/api/v1/subscriptions/plans", {
1401
+ ...request,
1402
+ currency: request.currency || "USD",
1403
+ interval_count: request.interval_count || 1,
1404
+ trial_days: request.trial_days || 0
1405
+ });
1406
+ }
1407
+ /**
1408
+ * Get subscription plan by ID
1409
+ */
1410
+ async getSubscriptionPlan(planId) {
1411
+ return this.request("GET", `/api/v1/subscriptions/plans/${planId}`);
1412
+ }
1413
+ /**
1414
+ * Create a subscription
1415
+ */
1416
+ async createSubscription(request) {
1417
+ return this.request("POST", "/api/v1/subscriptions", request);
1418
+ }
1419
+ /**
1420
+ * Get subscription by ID
1421
+ */
1422
+ async getSubscription(subscriptionId) {
1423
+ return this.request("GET", `/api/v1/subscriptions/${subscriptionId}`);
1424
+ }
1425
+ /**
1426
+ * Cancel a subscription
1427
+ */
1428
+ async cancelSubscription(subscriptionId) {
1429
+ return this.request(
1430
+ "POST",
1431
+ `/api/v1/subscriptions/${subscriptionId}/cancel`
1432
+ );
1433
+ }
1434
+ /**
1435
+ * Create a payment link (shareable checkout URL)
1436
+ */
1437
+ async createPaymentLink(request) {
1438
+ const response = await this.request("POST", "/api/v1/payment-links", {
1439
+ ...request,
1440
+ currency: request.currency || "USD",
1441
+ token: request.token || "USDC"
1442
+ });
1443
+ return {
1444
+ ...response,
1445
+ url: response.hosted_page_url
1446
+ };
1447
+ }
1448
+ /**
1449
+ * Get payment link by link code
1450
+ */
1451
+ async getPaymentLink(linkCode) {
1452
+ const response = await this.request("GET", `/api/v1/payment-links/${linkCode}`);
1453
+ return {
1454
+ ...response,
1455
+ url: response.hosted_page_url
1456
+ };
1457
+ }
1458
+ /**
1459
+ * List all payment links for the authenticated merchant
1460
+ */
1461
+ async listPaymentLinks() {
1462
+ const response = await this.request("GET", "/api/v1/payment-links");
1463
+ return response.map((link) => ({
1464
+ ...link,
1465
+ url: link.hosted_page_url
1466
+ }));
1467
+ }
1468
+ /**
1469
+ * Create an installment plan
1470
+ * Split a purchase into multiple scheduled payments
1471
+ */
1472
+ async createInstallmentPlan(request) {
1473
+ const response = await this.request(
1474
+ "POST",
1475
+ "/api/v1/installment-plans",
1476
+ request
1477
+ );
1478
+ return {
1479
+ id: response.plan_id,
1480
+ plan_id: response.plan_id,
1481
+ status: response.status
1482
+ };
1483
+ }
1484
+ /**
1485
+ * Get installment plan by ID
1486
+ */
1487
+ async getInstallmentPlan(planId) {
1488
+ return this.request("GET", `/api/v1/installment-plans/${planId}`);
1489
+ }
1490
+ /**
1491
+ * List all installment plans for merchant
1492
+ */
1493
+ async listInstallmentPlans(params) {
1494
+ const query = new URLSearchParams();
1495
+ if (params?.limit) query.append("limit", params.limit.toString());
1496
+ if (params?.offset) query.append("offset", params.offset.toString());
1497
+ const queryString = query.toString() ? `?${query.toString()}` : "";
1498
+ return this.request("GET", `/api/v1/installment-plans${queryString}`);
1499
+ }
1500
+ /**
1501
+ * List installment plans for a specific customer
1502
+ */
1503
+ async listCustomerInstallmentPlans(customerWallet) {
1504
+ return this.request(
1505
+ "GET",
1506
+ `/api/v1/customers/${customerWallet}/installment-plans`
1507
+ );
1508
+ }
1509
+ /**
1510
+ * Cancel an installment plan
1511
+ */
1512
+ async cancelInstallmentPlan(planId) {
1513
+ return this.request(
1514
+ "POST",
1515
+ `/api/v1/installment-plans/${planId}/cancel`
1516
+ );
1517
+ }
1518
+ /**
1519
+ * Create an escrow transaction
1520
+ * Hold funds until conditions are met
1521
+ */
1522
+ async createEscrow(request) {
1523
+ return this.request("POST", "/api/v1/escrows", {
1524
+ ...request,
1525
+ currency: request.currency || "USD",
1526
+ token: request.token || "USDC"
1527
+ });
1528
+ }
1529
+ /**
1530
+ * Get escrow by ID
1531
+ */
1532
+ async getEscrow(escrowId) {
1533
+ return this.request("GET", `/api/v1/escrows/${escrowId}`);
1534
+ }
1535
+ /**
1536
+ * List all escrows for merchant
1537
+ */
1538
+ async listEscrows(params) {
1539
+ const query = new URLSearchParams();
1540
+ if (params?.limit) query.append("limit", params.limit.toString());
1541
+ if (params?.offset) query.append("offset", params.offset.toString());
1542
+ const queryString = query.toString() ? `?${query.toString()}` : "";
1543
+ return this.request("GET", `/api/v1/escrows${queryString}`);
1544
+ }
1545
+ /**
1546
+ * Approve escrow release to seller
1547
+ */
1548
+ async approveEscrow(escrowId, request) {
1549
+ return this.request(
1550
+ "POST",
1551
+ `/api/v1/escrows/${escrowId}/approve`,
1552
+ request
1553
+ );
1554
+ }
1555
+ /**
1556
+ * Refund escrow to buyer
1557
+ */
1558
+ async refundEscrow(escrowId, request) {
1559
+ return this.request("POST", `/api/v1/escrows/${escrowId}/refund`, request);
1560
+ }
1561
+ /**
1562
+ * Raise a dispute for an escrow
1563
+ */
1564
+ async disputeEscrow(escrowId, request) {
1565
+ return this.request("POST", `/api/v1/escrows/${escrowId}/dispute`, request);
1566
+ }
1567
+ /**
1568
+ * Create an invoice
1569
+ */
1570
+ async createInvoice(request) {
1571
+ return this.request("POST", "/api/v1/invoices", {
1572
+ ...request,
1573
+ token: request.token || "USDC"
1574
+ });
1575
+ }
1576
+ /**
1577
+ * Get invoice by ID
1578
+ */
1579
+ async getInvoice(invoiceId) {
1580
+ return this.request("GET", `/api/v1/invoices/${invoiceId}`);
1581
+ }
1582
+ /**
1583
+ * List all invoices for merchant
1584
+ */
1585
+ async listInvoices() {
1586
+ return this.request("GET", "/api/v1/invoices");
1587
+ }
1588
+ /**
1589
+ * Send invoice to customer via email
1590
+ */
1591
+ async sendInvoice(invoiceId) {
1592
+ return this.request("POST", `/api/v1/invoices/${invoiceId}/send`);
1593
+ }
1594
+ // ============================================
1595
+ // Agentic Intent Protocol - Smart Payments
1596
+ // ============================================
1597
+ /**
1598
+ * Execute an AI-powered smart payment
1599
+ *
1600
+ * Smart payments combine multiple features:
1601
+ * - Automatic PPP pricing adjustments
1602
+ * - Gasless transaction detection
1603
+ * - Instant settlement options
1604
+ * - Escrow integration
1605
+ * - Receipt generation
1606
+ *
1607
+ * @param request - Smart payment configuration
1608
+ * @returns Payment result with status and receipt
1609
+ *
1610
+ * @example
1611
+ * ```typescript
1612
+ * const result = await zendfi.smartPayment({
1613
+ * agent_id: 'shopping-assistant',
1614
+ * user_wallet: 'Hx7B...abc',
1615
+ * amount_usd: 50,
1616
+ * auto_detect_gasless: true,
1617
+ * description: 'Premium subscription',
1618
+ * });
1619
+ *
1620
+ * if (result.requires_signature) {
1621
+ * // Device-bound flow: user needs to sign
1622
+ * console.log('Sign and submit:', result.submit_url);
1623
+ * } else {
1624
+ * // Auto-signed
1625
+ * console.log('Payment complete:', result.transaction_signature);
1626
+ * }
1627
+ * ```
1628
+ */
1629
+ async smartPayment(request) {
1630
+ return this.smart.execute(request);
1631
+ }
1632
+ /**
1633
+ * Submit a signed transaction for device-bound smart payment
1634
+ *
1635
+ * @param paymentId - UUID of the payment
1636
+ * @param signedTransaction - Base64 encoded signed transaction
1637
+ * @returns Updated payment response
1638
+ */
1639
+ async submitSignedPayment(paymentId, signedTransaction) {
1640
+ return this.smart.submitSigned(paymentId, signedTransaction);
1641
+ }
1642
+ /**
1643
+ * Verify webhook signature using HMAC-SHA256
1644
+ *
1645
+ * @param request - Webhook verification request containing payload, signature, and secret
1646
+ * @returns true if signature is valid, false otherwise
1647
+ *
1648
+ * @example
1649
+ * ```typescript
1650
+ * const isValid = zendfi.verifyWebhook({
1651
+ * payload: req.body,
1652
+ * signature: req.headers['x-zendfi-signature'],
1653
+ * secret: process.env.ZENDFI_WEBHOOK_SECRET
1654
+ * });
1655
+ *
1656
+ * if (!isValid) {
1657
+ * return res.status(401).json({ error: 'Invalid signature' });
1658
+ * }
1659
+ * ```
1660
+ */
1661
+ verifyWebhook(request) {
1662
+ try {
1663
+ if (!request.payload || !request.signature || !request.secret) {
1664
+ return false;
1665
+ }
1666
+ let payloadString;
469
1667
  let parsedPayload = null;
470
1668
  if (typeof request.payload === "string") {
471
1669
  payloadString = request.payload;
@@ -474,203 +1672,1363 @@ var ZendFiClient = class {
474
1672
  } catch (e) {
475
1673
  return false;
476
1674
  }
477
- } else if (typeof request.payload === "object") {
478
- parsedPayload = request.payload;
1675
+ } else if (typeof request.payload === "object") {
1676
+ parsedPayload = request.payload;
1677
+ try {
1678
+ payloadString = JSON.stringify(request.payload);
1679
+ } catch (e) {
1680
+ return false;
1681
+ }
1682
+ } else {
1683
+ return false;
1684
+ }
1685
+ if (!parsedPayload || !parsedPayload.event || !parsedPayload.merchant_id || !parsedPayload.timestamp) {
1686
+ return false;
1687
+ }
1688
+ const computedSignature = this.computeHmacSignature(payloadString, request.secret);
1689
+ return this.timingSafeEqual(request.signature, computedSignature);
1690
+ } catch (err) {
1691
+ const error = err;
1692
+ if (this.config.environment === "development") {
1693
+ console.error("Webhook verification error:", error?.message || String(error));
1694
+ }
1695
+ return false;
1696
+ }
1697
+ }
1698
+ /**
1699
+ * Compute HMAC-SHA256 signature
1700
+ * Works in both Node.js and browser environments
1701
+ */
1702
+ computeHmacSignature(payload, secret) {
1703
+ if (typeof process !== "undefined" && process.versions?.node) {
1704
+ return createHmac("sha256", secret).update(payload, "utf8").digest("hex");
1705
+ }
1706
+ throw new Error(
1707
+ "Webhook verification in browser is not supported. Use this method in your backend/server environment."
1708
+ );
1709
+ }
1710
+ /**
1711
+ * Timing-safe string comparison to prevent timing attacks
1712
+ */
1713
+ timingSafeEqual(a, b) {
1714
+ if (a.length !== b.length) {
1715
+ return false;
1716
+ }
1717
+ if (typeof process !== "undefined" && process.versions?.node) {
1718
+ try {
1719
+ const bufferA = Buffer.from(a, "utf8");
1720
+ const bufferB = Buffer.from(b, "utf8");
1721
+ return timingSafeEqual(bufferA, bufferB);
1722
+ } catch {
1723
+ }
1724
+ }
1725
+ let result = 0;
1726
+ for (let i = 0; i < a.length; i++) {
1727
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
1728
+ }
1729
+ return result === 0;
1730
+ }
1731
+ /**
1732
+ * Make an HTTP request with retry logic, interceptors, and debug logging
1733
+ */
1734
+ async request(method, endpoint, data, options = {}) {
1735
+ const attempt = options.attempt || 1;
1736
+ const idempotencyKey = options.idempotencyKey || (this.config.idempotencyEnabled && method !== "GET" ? generateIdempotencyKey() : void 0);
1737
+ const startTime = Date.now();
1738
+ try {
1739
+ const url = `${this.config.baseURL}${endpoint}`;
1740
+ const headers = {
1741
+ "Content-Type": "application/json",
1742
+ Authorization: `Bearer ${this.config.apiKey}`
1743
+ };
1744
+ if (idempotencyKey) {
1745
+ headers["Idempotency-Key"] = idempotencyKey;
1746
+ }
1747
+ let requestConfig = {
1748
+ method,
1749
+ url,
1750
+ headers,
1751
+ body: data
1752
+ };
1753
+ if (this.interceptors.request.has()) {
1754
+ requestConfig = await this.interceptors.request.execute(requestConfig);
1755
+ }
1756
+ if (this.config.debug) {
1757
+ console.log(`[ZendFi] ${method} ${endpoint}`);
1758
+ if (data) {
1759
+ console.log("[ZendFi] Request:", JSON.stringify(data, null, 2));
1760
+ }
1761
+ }
1762
+ const controller = new AbortController();
1763
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
1764
+ const response = await fetch2(requestConfig.url, {
1765
+ method: requestConfig.method,
1766
+ headers: requestConfig.headers,
1767
+ body: requestConfig.body ? JSON.stringify(requestConfig.body) : void 0,
1768
+ signal: controller.signal
1769
+ });
1770
+ clearTimeout(timeoutId);
1771
+ let body;
1772
+ try {
1773
+ body = await response.json();
1774
+ } catch {
1775
+ body = null;
1776
+ }
1777
+ const duration = Date.now() - startTime;
1778
+ if (!response.ok) {
1779
+ const error = createZendFiError(response.status, body);
1780
+ if (this.config.debug) {
1781
+ console.error(`[ZendFi] \u274C ${response.status} ${response.statusText} (${duration}ms)`);
1782
+ console.error(`[ZendFi] Error:`, error.toString());
1783
+ }
1784
+ if (response.status >= 500 && attempt < this.config.retries) {
1785
+ const delay = Math.pow(2, attempt) * 1e3;
1786
+ if (this.config.debug) {
1787
+ console.log(`[ZendFi] Retrying in ${delay}ms... (attempt ${attempt + 1}/${this.config.retries})`);
1788
+ }
1789
+ await sleep(delay);
1790
+ return this.request(method, endpoint, data, {
1791
+ idempotencyKey,
1792
+ attempt: attempt + 1
1793
+ });
1794
+ }
1795
+ if (this.interceptors.error.has()) {
1796
+ const interceptedError = await this.interceptors.error.execute(error);
1797
+ throw interceptedError;
1798
+ }
1799
+ throw error;
1800
+ }
1801
+ if (this.config.debug) {
1802
+ console.log(`[ZendFi] \u2713 ${response.status} ${response.statusText} (${duration}ms)`);
1803
+ if (body) {
1804
+ console.log("[ZendFi] Response:", JSON.stringify(body, null, 2));
1805
+ }
1806
+ }
1807
+ const headersObj = {};
1808
+ response.headers.forEach((value, key) => {
1809
+ headersObj[key] = value;
1810
+ });
1811
+ let responseData = {
1812
+ status: response.status,
1813
+ statusText: response.statusText,
1814
+ headers: headersObj,
1815
+ data: body,
1816
+ config: requestConfig
1817
+ };
1818
+ if (this.interceptors.response.has()) {
1819
+ responseData = await this.interceptors.response.execute(responseData);
1820
+ }
1821
+ return responseData.data;
1822
+ } catch (error) {
1823
+ if (error.name === "AbortError") {
1824
+ const timeoutError = createZendFiError(0, {}, `Request timeout after ${this.config.timeout}ms`);
1825
+ if (this.config.debug) {
1826
+ console.error(`[ZendFi] \u274C Timeout (${this.config.timeout}ms)`);
1827
+ }
1828
+ throw timeoutError;
1829
+ }
1830
+ if (attempt < this.config.retries && (error.message?.includes("fetch") || error.message?.includes("network"))) {
1831
+ const delay = Math.pow(2, attempt) * 1e3;
1832
+ if (this.config.debug) {
1833
+ console.log(`[ZendFi] Network error, retrying in ${delay}ms... (attempt ${attempt + 1}/${this.config.retries})`);
1834
+ }
1835
+ await sleep(delay);
1836
+ return this.request(method, endpoint, data, {
1837
+ idempotencyKey,
1838
+ attempt: attempt + 1
1839
+ });
1840
+ }
1841
+ if (isZendFiError(error)) {
1842
+ throw error;
1843
+ }
1844
+ const wrappedError = createZendFiError(0, {}, error.message || "An unknown error occurred");
1845
+ if (this.config.debug) {
1846
+ console.error(`[ZendFi] \u274C Unexpected error:`, error);
1847
+ }
1848
+ throw wrappedError;
1849
+ }
1850
+ }
1851
+ };
1852
+ var zendfi = (() => {
1853
+ try {
1854
+ return new ZendFiClient();
1855
+ } catch (error) {
1856
+ if (process.env.NODE_ENV === "test" || !process.env.ZENDFI_API_KEY) {
1857
+ return new Proxy({}, {
1858
+ get() {
1859
+ throw new Error(
1860
+ 'ZendFi singleton not initialized. Set ZENDFI_API_KEY environment variable or create a custom instance: new ZendFiClient({ apiKey: "..." })'
1861
+ );
1862
+ }
1863
+ });
1864
+ }
1865
+ throw error;
1866
+ }
1867
+ })();
1868
+
1869
+ // src/webhooks.ts
1870
+ async function verifyNextWebhook(request, secret) {
1871
+ try {
1872
+ const payload = await request.text();
1873
+ const signature = request.headers.get("x-zendfi-signature");
1874
+ if (!signature) {
1875
+ return null;
1876
+ }
1877
+ const webhookSecret = secret || process.env.ZENDFI_WEBHOOK_SECRET;
1878
+ if (!webhookSecret) {
1879
+ throw new Error("ZENDFI_WEBHOOK_SECRET not configured");
1880
+ }
1881
+ const isValid = zendfi.verifyWebhook({
1882
+ payload,
1883
+ signature,
1884
+ secret: webhookSecret
1885
+ });
1886
+ if (!isValid) {
1887
+ return null;
1888
+ }
1889
+ return JSON.parse(payload);
1890
+ } catch {
1891
+ return null;
1892
+ }
1893
+ }
1894
+ async function verifyExpressWebhook(request, secret) {
1895
+ try {
1896
+ const payload = request.rawBody || JSON.stringify(request.body);
1897
+ const signature = request.headers["x-zendfi-signature"];
1898
+ if (!signature) {
1899
+ return null;
1900
+ }
1901
+ const webhookSecret = secret || process.env.ZENDFI_WEBHOOK_SECRET;
1902
+ if (!webhookSecret) {
1903
+ throw new Error("ZENDFI_WEBHOOK_SECRET not configured");
1904
+ }
1905
+ const isValid = zendfi.verifyWebhook({
1906
+ payload,
1907
+ signature,
1908
+ secret: webhookSecret
1909
+ });
1910
+ if (!isValid) {
1911
+ return null;
1912
+ }
1913
+ return JSON.parse(payload);
1914
+ } catch {
1915
+ return null;
1916
+ }
1917
+ }
1918
+ function verifyWebhookSignature(payload, signature, secret) {
1919
+ return zendfi.verifyWebhook({
1920
+ payload,
1921
+ signature,
1922
+ secret
1923
+ });
1924
+ }
1925
+
1926
+ // src/device-bound-crypto.ts
1927
+ import { Keypair } from "@solana/web3.js";
1928
+ import * as crypto from "crypto";
1929
+ var DeviceFingerprintGenerator = class {
1930
+ /**
1931
+ * Generate a unique device fingerprint
1932
+ * Combines multiple browser attributes for uniqueness
1933
+ */
1934
+ static async generate() {
1935
+ const components = {};
1936
+ try {
1937
+ components.canvas = await this.getCanvasFingerprint();
1938
+ components.webgl = await this.getWebGLFingerprint();
1939
+ components.audio = await this.getAudioFingerprint();
1940
+ components.screen = `${screen.width}x${screen.height}x${screen.colorDepth}`;
1941
+ components.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
1942
+ components.languages = navigator.languages?.join(",") || navigator.language;
1943
+ components.platform = navigator.platform;
1944
+ components.hardwareConcurrency = navigator.hardwareConcurrency?.toString() || "unknown";
1945
+ const combined = Object.entries(components).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}:${value}`).join("|");
1946
+ const fingerprint = await this.sha256(combined);
1947
+ return {
1948
+ fingerprint,
1949
+ generatedAt: Date.now(),
1950
+ components
1951
+ };
1952
+ } catch (error) {
1953
+ console.warn("Device fingerprinting failed, using fallback", error);
1954
+ return this.generateFallbackFingerprint();
1955
+ }
1956
+ }
1957
+ /**
1958
+ * Graceful fallback fingerprint generation
1959
+ * Works in headless browsers, SSR, and restricted environments
1960
+ */
1961
+ static async generateFallbackFingerprint() {
1962
+ const components = {};
1963
+ try {
1964
+ if (typeof navigator !== "undefined") {
1965
+ components.platform = navigator.platform || "unknown";
1966
+ components.languages = navigator.languages?.join(",") || navigator.language || "unknown";
1967
+ components.hardwareConcurrency = navigator.hardwareConcurrency?.toString() || "unknown";
1968
+ }
1969
+ if (typeof screen !== "undefined") {
1970
+ components.screen = `${screen.width || 0}x${screen.height || 0}x${screen.colorDepth || 0}`;
1971
+ }
1972
+ if (typeof Intl !== "undefined") {
479
1973
  try {
480
- payloadString = JSON.stringify(request.payload);
481
- } catch (e) {
482
- return false;
1974
+ components.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
1975
+ } catch {
1976
+ components.timezone = "unknown";
483
1977
  }
484
- } else {
485
- return false;
486
1978
  }
487
- if (!parsedPayload || !parsedPayload.event || !parsedPayload.merchant_id || !parsedPayload.timestamp) {
488
- return false;
1979
+ } catch {
1980
+ components.platform = "fallback";
1981
+ }
1982
+ let randomEntropy = "";
1983
+ try {
1984
+ if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
1985
+ const arr = new Uint8Array(16);
1986
+ window.crypto.getRandomValues(arr);
1987
+ randomEntropy = Array.from(arr).map((b) => b.toString(16).padStart(2, "0")).join("");
1988
+ } else if (typeof crypto !== "undefined" && crypto.randomBytes) {
1989
+ randomEntropy = crypto.randomBytes(16).toString("hex");
489
1990
  }
490
- const computedSignature = this.computeHmacSignature(payloadString, request.secret);
491
- return this.timingSafeEqual(request.signature, computedSignature);
492
- } catch (err) {
493
- const error = err;
494
- if (this.config.environment === "development") {
495
- console.error("Webhook verification error:", error?.message || String(error));
1991
+ } catch {
1992
+ randomEntropy = Date.now().toString(36) + Math.random().toString(36);
1993
+ }
1994
+ const combined = Object.entries(components).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}:${value}`).join("|") + "|entropy:" + randomEntropy;
1995
+ const fingerprint = await this.sha256(combined);
1996
+ return {
1997
+ fingerprint,
1998
+ generatedAt: Date.now(),
1999
+ components
2000
+ };
2001
+ }
2002
+ static async getCanvasFingerprint() {
2003
+ const canvas = document.createElement("canvas");
2004
+ const ctx = canvas.getContext("2d");
2005
+ if (!ctx) return "no-canvas";
2006
+ canvas.width = 200;
2007
+ canvas.height = 50;
2008
+ ctx.textBaseline = "top";
2009
+ ctx.font = '14px "Arial"';
2010
+ ctx.fillStyle = "#f60";
2011
+ ctx.fillRect(0, 0, 100, 50);
2012
+ ctx.fillStyle = "#069";
2013
+ ctx.fillText("ZendFi \u{1F510}", 2, 2);
2014
+ ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
2015
+ ctx.fillText("Device-Bound", 4, 17);
2016
+ return canvas.toDataURL();
2017
+ }
2018
+ static async getWebGLFingerprint() {
2019
+ const canvas = document.createElement("canvas");
2020
+ const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
2021
+ if (!gl) return "no-webgl";
2022
+ const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
2023
+ if (!debugInfo) return "no-debug-info";
2024
+ const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
2025
+ const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
2026
+ return `${vendor}|${renderer}`;
2027
+ }
2028
+ static async getAudioFingerprint() {
2029
+ try {
2030
+ const AudioContext = window.AudioContext || window.webkitAudioContext;
2031
+ if (!AudioContext) return "no-audio";
2032
+ const context = new AudioContext();
2033
+ const oscillator = context.createOscillator();
2034
+ const analyser = context.createAnalyser();
2035
+ const gainNode = context.createGain();
2036
+ const scriptProcessor = context.createScriptProcessor(4096, 1, 1);
2037
+ gainNode.gain.value = 0;
2038
+ oscillator.connect(analyser);
2039
+ analyser.connect(scriptProcessor);
2040
+ scriptProcessor.connect(gainNode);
2041
+ gainNode.connect(context.destination);
2042
+ oscillator.start(0);
2043
+ return new Promise((resolve) => {
2044
+ scriptProcessor.onaudioprocess = (event) => {
2045
+ const output = event.inputBuffer.getChannelData(0);
2046
+ const hash = Array.from(output.slice(0, 30)).reduce((acc, val) => acc + Math.abs(val), 0);
2047
+ oscillator.stop();
2048
+ scriptProcessor.disconnect();
2049
+ context.close();
2050
+ resolve(hash.toString());
2051
+ };
2052
+ });
2053
+ } catch (error) {
2054
+ return "audio-error";
2055
+ }
2056
+ }
2057
+ static async sha256(data) {
2058
+ if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
2059
+ const encoder = new TextEncoder();
2060
+ const dataBuffer = encoder.encode(data);
2061
+ const hashBuffer = await window.crypto.subtle.digest("SHA-256", dataBuffer);
2062
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
2063
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
2064
+ } else {
2065
+ return crypto.createHash("sha256").update(data).digest("hex");
2066
+ }
2067
+ }
2068
+ };
2069
+ var SessionKeyCrypto = class {
2070
+ /**
2071
+ * Encrypt a Solana keypair with PIN + device fingerprint
2072
+ * Uses Argon2id for key derivation and AES-256-GCM for encryption
2073
+ */
2074
+ static async encrypt(keypair, pin, deviceFingerprint) {
2075
+ if (!/^\d{6}$/.test(pin)) {
2076
+ throw new Error("PIN must be exactly 6 numeric digits");
2077
+ }
2078
+ const encryptionKey = await this.deriveKey(pin, deviceFingerprint);
2079
+ const nonce = this.generateNonce();
2080
+ const secretKey = keypair.secretKey;
2081
+ const encryptedData = await this.aesEncrypt(secretKey, encryptionKey, nonce);
2082
+ return {
2083
+ encryptedData: Buffer.from(encryptedData).toString("base64"),
2084
+ nonce: Buffer.from(nonce).toString("base64"),
2085
+ publicKey: keypair.publicKey.toBase58(),
2086
+ deviceFingerprint,
2087
+ version: "argon2id-aes256gcm-v1"
2088
+ };
2089
+ }
2090
+ /**
2091
+ * Decrypt an encrypted session key with PIN + device fingerprint
2092
+ */
2093
+ static async decrypt(encrypted, pin, deviceFingerprint) {
2094
+ if (!/^\d{6}$/.test(pin)) {
2095
+ throw new Error("PIN must be exactly 6 numeric digits");
2096
+ }
2097
+ if (encrypted.deviceFingerprint !== deviceFingerprint) {
2098
+ throw new Error("Device fingerprint mismatch - wrong device or security threat");
2099
+ }
2100
+ const encryptionKey = await this.deriveKey(pin, deviceFingerprint);
2101
+ const encryptedData = Buffer.from(encrypted.encryptedData, "base64");
2102
+ const nonce = Buffer.from(encrypted.nonce, "base64");
2103
+ try {
2104
+ const secretKey = await this.aesDecrypt(encryptedData, encryptionKey, nonce);
2105
+ return Keypair.fromSecretKey(secretKey);
2106
+ } catch (error) {
2107
+ throw new Error("Decryption failed - wrong PIN or corrupted data");
2108
+ }
2109
+ }
2110
+ /**
2111
+ * Derive encryption key from PIN + device fingerprint using Argon2id
2112
+ *
2113
+ * Argon2id parameters (OWASP recommended):
2114
+ * - Memory: 64MB (65536 KB)
2115
+ * - Iterations: 3
2116
+ * - Parallelism: 4
2117
+ * - Salt: device fingerprint
2118
+ */
2119
+ static async deriveKey(pin, deviceFingerprint) {
2120
+ if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
2121
+ const encoder = new TextEncoder();
2122
+ const keyMaterial = await window.crypto.subtle.importKey(
2123
+ "raw",
2124
+ encoder.encode(pin),
2125
+ { name: "PBKDF2" },
2126
+ false,
2127
+ ["deriveBits"]
2128
+ );
2129
+ const derivedBits = await window.crypto.subtle.deriveBits(
2130
+ {
2131
+ name: "PBKDF2",
2132
+ salt: encoder.encode(deviceFingerprint),
2133
+ iterations: 1e5,
2134
+ // High iteration count for security
2135
+ hash: "SHA-256"
2136
+ },
2137
+ keyMaterial,
2138
+ 256
2139
+ // 256 bits = 32 bytes for AES-256
2140
+ );
2141
+ return new Uint8Array(derivedBits);
2142
+ } else {
2143
+ const salt = crypto.createHash("sha256").update(deviceFingerprint).digest();
2144
+ return crypto.pbkdf2Sync(pin, salt, 1e5, 32, "sha256");
2145
+ }
2146
+ }
2147
+ /**
2148
+ * Generate random nonce for AES-GCM (12 bytes)
2149
+ */
2150
+ static generateNonce() {
2151
+ if (typeof window !== "undefined" && window.crypto) {
2152
+ return window.crypto.getRandomValues(new Uint8Array(12));
2153
+ } else {
2154
+ return crypto.randomBytes(12);
2155
+ }
2156
+ }
2157
+ /**
2158
+ * Encrypt with AES-256-GCM
2159
+ */
2160
+ static async aesEncrypt(plaintext, key, nonce) {
2161
+ if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
2162
+ const keyBuffer = key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength);
2163
+ const nonceBuffer = nonce.buffer.slice(nonce.byteOffset, nonce.byteOffset + nonce.byteLength);
2164
+ const plaintextBuffer = plaintext.buffer.slice(plaintext.byteOffset, plaintext.byteOffset + plaintext.byteLength);
2165
+ const cryptoKey = await window.crypto.subtle.importKey(
2166
+ "raw",
2167
+ keyBuffer,
2168
+ { name: "AES-GCM" },
2169
+ false,
2170
+ ["encrypt"]
2171
+ );
2172
+ const encrypted = await window.crypto.subtle.encrypt(
2173
+ {
2174
+ name: "AES-GCM",
2175
+ iv: nonceBuffer
2176
+ },
2177
+ cryptoKey,
2178
+ plaintextBuffer
2179
+ );
2180
+ return new Uint8Array(encrypted);
2181
+ } else {
2182
+ const cipher = crypto.createCipheriv("aes-256-gcm", key, nonce);
2183
+ const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
2184
+ const authTag = cipher.getAuthTag();
2185
+ return new Uint8Array(Buffer.concat([encrypted, authTag]));
2186
+ }
2187
+ }
2188
+ /**
2189
+ * Decrypt with AES-256-GCM
2190
+ */
2191
+ static async aesDecrypt(ciphertext, key, nonce) {
2192
+ if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
2193
+ const keyBuffer = key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength);
2194
+ const nonceBuffer = nonce.buffer.slice(nonce.byteOffset, nonce.byteOffset + nonce.byteLength);
2195
+ const ciphertextBuffer = ciphertext.buffer.slice(ciphertext.byteOffset, ciphertext.byteOffset + ciphertext.byteLength);
2196
+ const cryptoKey = await window.crypto.subtle.importKey(
2197
+ "raw",
2198
+ keyBuffer,
2199
+ { name: "AES-GCM" },
2200
+ false,
2201
+ ["decrypt"]
2202
+ );
2203
+ const decrypted = await window.crypto.subtle.decrypt(
2204
+ {
2205
+ name: "AES-GCM",
2206
+ iv: nonceBuffer
2207
+ },
2208
+ cryptoKey,
2209
+ ciphertextBuffer
2210
+ );
2211
+ return new Uint8Array(decrypted);
2212
+ } else {
2213
+ const authTag = ciphertext.slice(-16);
2214
+ const encrypted = ciphertext.slice(0, -16);
2215
+ const decipher = crypto.createDecipheriv("aes-256-gcm", key, nonce);
2216
+ decipher.setAuthTag(authTag);
2217
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
2218
+ return new Uint8Array(decrypted);
2219
+ }
2220
+ }
2221
+ };
2222
+ var RecoveryQRGenerator = class {
2223
+ /**
2224
+ * Generate recovery QR data
2225
+ * This allows users to recover their session key on a new device
2226
+ */
2227
+ static generate(encrypted) {
2228
+ return {
2229
+ encryptedSessionKey: encrypted.encryptedData,
2230
+ nonce: encrypted.nonce,
2231
+ publicKey: encrypted.publicKey,
2232
+ version: "v1",
2233
+ createdAt: Date.now()
2234
+ };
2235
+ }
2236
+ /**
2237
+ * Encode recovery QR as JSON string
2238
+ */
2239
+ static encode(recoveryQR) {
2240
+ return JSON.stringify(recoveryQR);
2241
+ }
2242
+ /**
2243
+ * Decode recovery QR from JSON string
2244
+ */
2245
+ static decode(qrData) {
2246
+ try {
2247
+ const parsed = JSON.parse(qrData);
2248
+ if (!parsed.encryptedSessionKey || !parsed.nonce || !parsed.publicKey) {
2249
+ throw new Error("Invalid recovery QR data");
2250
+ }
2251
+ return parsed;
2252
+ } catch (error) {
2253
+ throw new Error("Failed to decode recovery QR");
2254
+ }
2255
+ }
2256
+ /**
2257
+ * Re-encrypt session key for new device
2258
+ */
2259
+ static async reEncryptForNewDevice(recoveryQR, oldPin, oldDeviceFingerprint, newPin, newDeviceFingerprint) {
2260
+ const oldEncrypted = {
2261
+ encryptedData: recoveryQR.encryptedSessionKey,
2262
+ nonce: recoveryQR.nonce,
2263
+ publicKey: recoveryQR.publicKey,
2264
+ deviceFingerprint: oldDeviceFingerprint,
2265
+ version: "argon2id-aes256gcm-v1"
2266
+ };
2267
+ const keypair = await SessionKeyCrypto.decrypt(oldEncrypted, oldPin, oldDeviceFingerprint);
2268
+ return await SessionKeyCrypto.encrypt(keypair, newPin, newDeviceFingerprint);
2269
+ }
2270
+ };
2271
+ var DeviceBoundSessionKey = class _DeviceBoundSessionKey {
2272
+ encrypted = null;
2273
+ deviceFingerprint = null;
2274
+ sessionKeyId = null;
2275
+ recoveryQR = null;
2276
+ // Auto-signing cache: decrypted keypair stored in memory
2277
+ // Enables instant signing without re-entering PIN for subsequent payments
2278
+ cachedKeypair = null;
2279
+ cacheExpiry = null;
2280
+ // Timestamp when cache expires
2281
+ DEFAULT_CACHE_TTL_MS = 30 * 60 * 1e3;
2282
+ // 30 minutes
2283
+ /**
2284
+ * Create a new device-bound session key
2285
+ */
2286
+ static async create(options) {
2287
+ const deviceFingerprint = await DeviceFingerprintGenerator.generate();
2288
+ const keypair = Keypair.generate();
2289
+ const encrypted = await SessionKeyCrypto.encrypt(
2290
+ keypair,
2291
+ options.pin,
2292
+ deviceFingerprint.fingerprint
2293
+ );
2294
+ const instance = new _DeviceBoundSessionKey();
2295
+ instance.encrypted = encrypted;
2296
+ instance.deviceFingerprint = deviceFingerprint;
2297
+ if (options.generateRecoveryQR) {
2298
+ instance.recoveryQR = RecoveryQRGenerator.generate(encrypted);
2299
+ }
2300
+ return instance;
2301
+ }
2302
+ /**
2303
+ * Get encrypted data for backend storage
2304
+ */
2305
+ getEncryptedData() {
2306
+ if (!this.encrypted) {
2307
+ throw new Error("Session key not created yet");
2308
+ }
2309
+ return this.encrypted;
2310
+ }
2311
+ /**
2312
+ * Get device fingerprint
2313
+ */
2314
+ getDeviceFingerprint() {
2315
+ if (!this.deviceFingerprint) {
2316
+ throw new Error("Device fingerprint not generated yet");
2317
+ }
2318
+ return this.deviceFingerprint.fingerprint;
2319
+ }
2320
+ /**
2321
+ * Get public key
2322
+ */
2323
+ getPublicKey() {
2324
+ if (!this.encrypted) {
2325
+ throw new Error("Session key not created yet");
2326
+ }
2327
+ return this.encrypted.publicKey;
2328
+ }
2329
+ /**
2330
+ * Get recovery QR data (if generated)
2331
+ */
2332
+ getRecoveryQR() {
2333
+ return this.recoveryQR;
2334
+ }
2335
+ /**
2336
+ * Decrypt and sign a transaction
2337
+ *
2338
+ * @param transaction - The transaction to sign
2339
+ * @param pin - User's PIN (required only if keypair not cached)
2340
+ * @param cacheKeypair - Whether to cache the decrypted keypair for future use (default: true)
2341
+ * @param cacheTTL - Cache time-to-live in milliseconds (default: 30 minutes)
2342
+ *
2343
+ * @example
2344
+ * ```typescript
2345
+ * // First payment: requires PIN, caches keypair
2346
+ * await sessionKey.signTransaction(tx1, '123456', true);
2347
+ *
2348
+ * // Subsequent payments: uses cached keypair, no PIN needed!
2349
+ * await sessionKey.signTransaction(tx2, '', false); // PIN ignored if cached
2350
+ *
2351
+ * // Clear cache when done
2352
+ * sessionKey.clearCache();
2353
+ * ```
2354
+ */
2355
+ async signTransaction(transaction, pin = "", cacheKeypair = true, cacheTTL) {
2356
+ if (!this.encrypted || !this.deviceFingerprint) {
2357
+ throw new Error("Session key not initialized");
2358
+ }
2359
+ let keypair;
2360
+ if (this.isCached()) {
2361
+ keypair = this.cachedKeypair;
2362
+ if (typeof console !== "undefined") {
2363
+ console.log("\u{1F680} Using cached keypair - instant signing (no PIN required)");
2364
+ }
2365
+ } else {
2366
+ if (!pin) {
2367
+ throw new Error("PIN required: no cached keypair available");
2368
+ }
2369
+ keypair = await SessionKeyCrypto.decrypt(
2370
+ this.encrypted,
2371
+ pin,
2372
+ this.deviceFingerprint.fingerprint
2373
+ );
2374
+ if (cacheKeypair) {
2375
+ const ttl = cacheTTL || this.DEFAULT_CACHE_TTL_MS;
2376
+ this.cacheKeypair(keypair, ttl);
2377
+ if (typeof console !== "undefined") {
2378
+ console.log(`\u2705 Keypair decrypted and cached for ${ttl / 1e3 / 60} minutes`);
2379
+ }
2380
+ }
2381
+ }
2382
+ transaction.sign(keypair);
2383
+ return transaction;
2384
+ }
2385
+ /**
2386
+ * Check if keypair is cached and valid
2387
+ */
2388
+ isCached() {
2389
+ if (!this.cachedKeypair || !this.cacheExpiry) {
2390
+ return false;
2391
+ }
2392
+ const now = Date.now();
2393
+ if (now > this.cacheExpiry) {
2394
+ this.clearCache();
2395
+ return false;
2396
+ }
2397
+ return true;
2398
+ }
2399
+ /**
2400
+ * Manually cache a keypair
2401
+ * Called internally after PIN decryption
2402
+ */
2403
+ cacheKeypair(keypair, ttl) {
2404
+ this.cachedKeypair = keypair;
2405
+ this.cacheExpiry = Date.now() + ttl;
2406
+ }
2407
+ /**
2408
+ * Clear cached keypair
2409
+ * Should be called when user logs out or session ends
2410
+ *
2411
+ * @example
2412
+ * ```typescript
2413
+ * // Clear cache on logout
2414
+ * sessionKey.clearCache();
2415
+ *
2416
+ * // Or clear automatically on tab close
2417
+ * window.addEventListener('beforeunload', () => {
2418
+ * sessionKey.clearCache();
2419
+ * });
2420
+ * ```
2421
+ */
2422
+ clearCache() {
2423
+ this.cachedKeypair = null;
2424
+ this.cacheExpiry = null;
2425
+ if (typeof console !== "undefined") {
2426
+ console.log("\u{1F9F9} Keypair cache cleared");
2427
+ }
2428
+ }
2429
+ /**
2430
+ * Decrypt and cache keypair without signing a transaction
2431
+ * Useful for pre-warming the cache before user makes payments
2432
+ *
2433
+ * @example
2434
+ * ```typescript
2435
+ * // After session key creation, decrypt and cache
2436
+ * await sessionKey.unlockWithPin('123456');
2437
+ *
2438
+ * // Now all subsequent payments are instant (no PIN)
2439
+ * await sessionKey.signTransaction(tx1, '', false); // Instant!
2440
+ * await sessionKey.signTransaction(tx2, '', false); // Instant!
2441
+ * ```
2442
+ */
2443
+ async unlockWithPin(pin, cacheTTL) {
2444
+ if (!this.encrypted || !this.deviceFingerprint) {
2445
+ throw new Error("Session key not initialized");
2446
+ }
2447
+ const keypair = await SessionKeyCrypto.decrypt(
2448
+ this.encrypted,
2449
+ pin,
2450
+ this.deviceFingerprint.fingerprint
2451
+ );
2452
+ const ttl = cacheTTL || this.DEFAULT_CACHE_TTL_MS;
2453
+ this.cacheKeypair(keypair, ttl);
2454
+ if (typeof console !== "undefined") {
2455
+ console.log(`\u{1F513} Session key unlocked and cached for ${ttl / 1e3 / 60} minutes`);
2456
+ }
2457
+ }
2458
+ /**
2459
+ * Get time remaining until cache expires (in milliseconds)
2460
+ * Returns 0 if not cached
2461
+ */
2462
+ getCacheTimeRemaining() {
2463
+ if (!this.isCached() || !this.cacheExpiry) {
2464
+ return 0;
2465
+ }
2466
+ const remaining = this.cacheExpiry - Date.now();
2467
+ return Math.max(0, remaining);
2468
+ }
2469
+ /**
2470
+ * Extend cache expiry time
2471
+ * Useful to keep session active during user activity
2472
+ *
2473
+ * @example
2474
+ * ```typescript
2475
+ * // Extend cache by 15 minutes on each payment
2476
+ * await sessionKey.signTransaction(tx, '');
2477
+ * sessionKey.extendCache(15 * 60 * 1000);
2478
+ * ```
2479
+ */
2480
+ extendCache(additionalTTL) {
2481
+ if (!this.isCached()) {
2482
+ throw new Error("Cannot extend cache: no cached keypair");
2483
+ }
2484
+ this.cacheExpiry += additionalTTL;
2485
+ if (typeof console !== "undefined") {
2486
+ const remainingMinutes = this.getCacheTimeRemaining() / 1e3 / 60;
2487
+ console.log(`\u23F0 Cache extended - ${remainingMinutes.toFixed(1)} minutes remaining`);
2488
+ }
2489
+ }
2490
+ /**
2491
+ * Set session key ID after backend creation
2492
+ */
2493
+ setSessionKeyId(id) {
2494
+ this.sessionKeyId = id;
2495
+ }
2496
+ /**
2497
+ * Get session key ID
2498
+ */
2499
+ getSessionKeyId() {
2500
+ if (!this.sessionKeyId) {
2501
+ throw new Error("Session key not registered with backend");
2502
+ }
2503
+ return this.sessionKeyId;
2504
+ }
2505
+ };
2506
+
2507
+ // src/device-bound-session-keys.ts
2508
+ import { Transaction as Transaction2 } from "@solana/web3.js";
2509
+ var ZendFiSessionKeyManager = class {
2510
+ baseURL;
2511
+ apiKey;
2512
+ sessionKey = null;
2513
+ sessionKeyId = null;
2514
+ constructor(apiKey, baseURL = "https://api.zendfi.com") {
2515
+ this.apiKey = apiKey;
2516
+ this.baseURL = baseURL;
2517
+ }
2518
+ /**
2519
+ * Create a new device-bound session key
2520
+ *
2521
+ * @example
2522
+ * ```typescript
2523
+ * const manager = new ZendFiSessionKeyManager('your-api-key');
2524
+ *
2525
+ * const sessionKey = await manager.createSessionKey({
2526
+ * userWallet: '7xKNH....',
2527
+ * limitUSDC: 100,
2528
+ * durationDays: 7,
2529
+ * pin: '123456',
2530
+ * generateRecoveryQR: true,
2531
+ * });
2532
+ *
2533
+ * console.log('Session key created:', sessionKey.sessionKeyId);
2534
+ * console.log('Recovery QR:', sessionKey.recoveryQR);
2535
+ * ```
2536
+ */
2537
+ async createSessionKey(options) {
2538
+ const sessionKey = await DeviceBoundSessionKey.create({
2539
+ pin: options.pin,
2540
+ limitUSDC: options.limitUSDC,
2541
+ durationDays: options.durationDays,
2542
+ userWallet: options.userWallet,
2543
+ generateRecoveryQR: options.generateRecoveryQR
2544
+ });
2545
+ const encrypted = sessionKey.getEncryptedData();
2546
+ let recoveryQR;
2547
+ if (options.generateRecoveryQR) {
2548
+ const qr = RecoveryQRGenerator.generate(encrypted);
2549
+ recoveryQR = RecoveryQRGenerator.encode(qr);
2550
+ }
2551
+ const request = {
2552
+ userWallet: options.userWallet,
2553
+ limitUsdc: options.limitUSDC,
2554
+ durationDays: options.durationDays,
2555
+ encryptedSessionKey: encrypted.encryptedData,
2556
+ nonce: encrypted.nonce,
2557
+ sessionPublicKey: encrypted.publicKey,
2558
+ deviceFingerprint: sessionKey.getDeviceFingerprint(),
2559
+ recoveryQrData: recoveryQR
2560
+ };
2561
+ const response = await this.request(
2562
+ "POST",
2563
+ "/api/v1/ai/session-keys/device-bound/create",
2564
+ request
2565
+ );
2566
+ this.sessionKey = sessionKey;
2567
+ this.sessionKeyId = response.sessionKeyId;
2568
+ sessionKey.setSessionKeyId(response.sessionKeyId);
2569
+ return {
2570
+ sessionKeyId: response.sessionKeyId,
2571
+ sessionWallet: response.sessionWallet,
2572
+ expiresAt: response.expiresAt,
2573
+ recoveryQR,
2574
+ limitUsdc: response.limitUsdc
2575
+ };
2576
+ }
2577
+ /**
2578
+ * Load an existing session key from backend
2579
+ * Requires PIN to decrypt
2580
+ */
2581
+ async loadSessionKey(sessionKeyId, pin) {
2582
+ const deviceInfo = await DeviceFingerprintGenerator.generate();
2583
+ const response = await this.request(
2584
+ "POST",
2585
+ "/api/v1/ai/session-keys/device-bound/get-encrypted",
2586
+ {
2587
+ sessionKeyId,
2588
+ deviceFingerprint: deviceInfo.fingerprint
496
2589
  }
497
- return false;
2590
+ );
2591
+ if (!response.deviceFingerprintValid) {
2592
+ throw new Error(
2593
+ "Device fingerprint mismatch - this session key was created on a different device. Use recovery QR to migrate."
2594
+ );
498
2595
  }
2596
+ const encrypted = {
2597
+ encryptedData: response.encryptedSessionKey,
2598
+ nonce: response.nonce,
2599
+ publicKey: "",
2600
+ // Will be populated after decryption
2601
+ deviceFingerprint: deviceInfo.fingerprint,
2602
+ version: "argon2id-aes256gcm-v1"
2603
+ };
2604
+ const keypair = await SessionKeyCrypto.decrypt(encrypted, pin, deviceInfo.fingerprint);
2605
+ encrypted.publicKey = keypair.publicKey.toBase58();
2606
+ this.sessionKey = new DeviceBoundSessionKey();
2607
+ this.sessionKey.encrypted = encrypted;
2608
+ this.sessionKey.deviceFingerprint = deviceInfo;
2609
+ this.sessionKey.setSessionKeyId(sessionKeyId);
2610
+ this.sessionKeyId = sessionKeyId;
499
2611
  }
500
2612
  /**
501
- * Compute HMAC-SHA256 signature
502
- * Works in both Node.js and browser environments
2613
+ * Make a payment using the session key
2614
+ *
2615
+ * First payment: Requires PIN to decrypt session key
2616
+ * Subsequent payments: Uses cached keypair (no PIN needed!) ✨
2617
+ *
2618
+ * @example
2619
+ * ```typescript
2620
+ * // First payment: requires PIN
2621
+ * const result1 = await manager.makePayment({
2622
+ * amount: 5.0,
2623
+ * recipient: '7xKNH....',
2624
+ * pin: '123456',
2625
+ * description: 'Coffee purchase',
2626
+ * });
2627
+ *
2628
+ * // Second payment: NO PIN NEEDED! Instant signing!
2629
+ * const result2 = await manager.makePayment({
2630
+ * amount: 3.0,
2631
+ * recipient: '7xKNH....',
2632
+ * description: 'Donut purchase',
2633
+ * }); // <- No PIN! Uses cached keypair
2634
+ *
2635
+ * console.log('Payment signature:', result2.signature);
2636
+ *
2637
+ * // Disable auto-signing for single payment
2638
+ * const result3 = await manager.makePayment({
2639
+ * amount: 100.0,
2640
+ * recipient: '7xKNH....',
2641
+ * pin: '123456',
2642
+ * enableAutoSign: false, // Will require PIN every time
2643
+ * });
2644
+ * ```
503
2645
  */
504
- computeHmacSignature(payload, secret) {
505
- if (typeof process !== "undefined" && process.versions?.node) {
506
- return createHmac("sha256", secret).update(payload, "utf8").digest("hex");
2646
+ async makePayment(options) {
2647
+ if (!this.sessionKey || !this.sessionKeyId) {
2648
+ throw new Error("No session key loaded. Call createSessionKey() or loadSessionKey() first.");
507
2649
  }
508
- throw new Error(
509
- "Webhook verification in browser is not supported. Use this method in your backend/server environment."
2650
+ const enableAutoSign = options.enableAutoSign !== false;
2651
+ const needsPin = !this.sessionKey.isCached();
2652
+ if (needsPin && !options.pin) {
2653
+ throw new Error(
2654
+ "PIN required: no cached keypair available. Please provide PIN or call unlockSessionKey() first."
2655
+ );
2656
+ }
2657
+ const paymentResponse = await this.request("POST", "/api/v1/ai/smart-payment", {
2658
+ amount_usd: options.amount,
2659
+ user_wallet: options.recipient,
2660
+ token: options.token || "USDC",
2661
+ description: options.description
2662
+ }, {
2663
+ "X-Session-Key-ID": this.sessionKeyId
2664
+ });
2665
+ if (!paymentResponse.requires_signature && paymentResponse.status === "confirmed") {
2666
+ return {
2667
+ paymentId: paymentResponse.paymentId,
2668
+ signature: "",
2669
+ // Backend signed
2670
+ status: paymentResponse.status
2671
+ };
2672
+ }
2673
+ if (!paymentResponse.unsigned_transaction) {
2674
+ throw new Error("Backend did not return unsigned transaction");
2675
+ }
2676
+ const transactionBuffer = Buffer.from(paymentResponse.unsigned_transaction, "base64");
2677
+ const transaction = Transaction2.from(transactionBuffer);
2678
+ const signedTransaction = await this.sessionKey.signTransaction(
2679
+ transaction,
2680
+ options.pin || "",
2681
+ // PIN only needed if not cached
2682
+ enableAutoSign
2683
+ // Cache for future payments
510
2684
  );
2685
+ const submitResponse = await this.request("POST", `/api/v1/ai/payments/${paymentResponse.paymentId}/submit-signed`, {
2686
+ signed_transaction: signedTransaction.serialize().toString("base64")
2687
+ });
2688
+ return {
2689
+ paymentId: paymentResponse.paymentId,
2690
+ signature: submitResponse.signature,
2691
+ status: submitResponse.status
2692
+ };
511
2693
  }
512
2694
  /**
513
- * Timing-safe string comparison to prevent timing attacks
2695
+ * Recover session key on new device
2696
+ * Requires recovery QR and PIN from original device
2697
+ *
2698
+ * @example
2699
+ * ```typescript
2700
+ * const recovered = await manager.recoverSessionKey({
2701
+ * sessionKeyId: 'uuid...',
2702
+ * recoveryQR: '{"encryptedSessionKey":"..."}',
2703
+ * oldPin: '123456',
2704
+ * newPin: '654321',
2705
+ * });
2706
+ * ```
514
2707
  */
515
- timingSafeEqual(a, b) {
516
- if (a.length !== b.length) {
517
- return false;
2708
+ async recoverSessionKey(options) {
2709
+ const recoveryData = RecoveryQRGenerator.decode(options.recoveryQR);
2710
+ const oldDeviceFingerprint = "recovery-mode";
2711
+ const newDeviceInfo = await DeviceFingerprintGenerator.generate();
2712
+ const newEncrypted = await RecoveryQRGenerator.reEncryptForNewDevice(
2713
+ recoveryData,
2714
+ options.oldPin,
2715
+ oldDeviceFingerprint,
2716
+ options.newPin,
2717
+ newDeviceInfo.fingerprint
2718
+ );
2719
+ await this.request("POST", `/api/v1/ai/session-keys/device-bound/${options.sessionKeyId}/recover`, {
2720
+ recoveryQrData: options.recoveryQR,
2721
+ newDeviceFingerprint: newDeviceInfo.fingerprint,
2722
+ newEncryptedSessionKey: newEncrypted.encryptedData,
2723
+ newNonce: newEncrypted.nonce
2724
+ });
2725
+ await this.loadSessionKey(options.sessionKeyId, options.newPin);
2726
+ }
2727
+ /**
2728
+ * Revoke session key
2729
+ */
2730
+ async revokeSessionKey(sessionKeyId) {
2731
+ const keyId = sessionKeyId || this.sessionKeyId;
2732
+ if (!keyId) {
2733
+ throw new Error("No session key ID provided");
518
2734
  }
519
- if (typeof process !== "undefined" && process.versions?.node) {
520
- try {
521
- const bufferA = Buffer.from(a, "utf8");
522
- const bufferB = Buffer.from(b, "utf8");
523
- return timingSafeEqual(bufferA, bufferB);
524
- } catch {
525
- }
2735
+ await this.request("POST", "/api/v1/ai/session-keys/revoke", {
2736
+ session_key_id: keyId
2737
+ });
2738
+ if (keyId === this.sessionKeyId) {
2739
+ this.sessionKey = null;
2740
+ this.sessionKeyId = null;
526
2741
  }
527
- let result = 0;
528
- for (let i = 0; i < a.length; i++) {
529
- result |= a.charCodeAt(i) ^ b.charCodeAt(i);
2742
+ }
2743
+ /**
2744
+ * Unlock session key with PIN and cache for auto-signing
2745
+ * Call this after creating/loading session key to enable instant payments
2746
+ *
2747
+ * @example
2748
+ * ```typescript
2749
+ * // Create session key
2750
+ * await manager.createSessionKey({...});
2751
+ *
2752
+ * // Unlock with PIN (one-time)
2753
+ * await manager.unlockSessionKey('123456');
2754
+ *
2755
+ * // Now all payments are instant (no PIN!)
2756
+ * await manager.makePayment({amount: 5, ...}); // Instant!
2757
+ * await manager.makePayment({amount: 3, ...}); // Instant!
2758
+ * ```
2759
+ */
2760
+ async unlockSessionKey(pin, cacheTTL) {
2761
+ if (!this.sessionKey) {
2762
+ throw new Error("No session key loaded");
530
2763
  }
531
- return result === 0;
2764
+ await this.sessionKey.unlockWithPin(pin, cacheTTL);
532
2765
  }
533
2766
  /**
534
- * Make an HTTP request with retry logic
2767
+ * Clear cached keypair
2768
+ * Should be called on logout or when session ends
2769
+ *
2770
+ * @example
2771
+ * ```typescript
2772
+ * // Clear on logout
2773
+ * manager.clearCache();
2774
+ *
2775
+ * // Or auto-clear on tab close
2776
+ * window.addEventListener('beforeunload', () => {
2777
+ * manager.clearCache();
2778
+ * });
2779
+ * ```
535
2780
  */
536
- async request(method, endpoint, data, options = {}) {
537
- const attempt = options.attempt || 1;
538
- const idempotencyKey = options.idempotencyKey || (this.config.idempotencyEnabled && method !== "GET" ? generateIdempotencyKey() : void 0);
2781
+ clearCache() {
2782
+ if (this.sessionKey) {
2783
+ this.sessionKey.clearCache();
2784
+ }
2785
+ }
2786
+ /**
2787
+ * Check if keypair is cached (auto-signing enabled)
2788
+ */
2789
+ isCached() {
2790
+ return this.sessionKey?.isCached() || false;
2791
+ }
2792
+ /**
2793
+ * Get time remaining until cache expires (in milliseconds)
2794
+ */
2795
+ getCacheTimeRemaining() {
2796
+ return this.sessionKey?.getCacheTimeRemaining() || 0;
2797
+ }
2798
+ /**
2799
+ * Extend cache expiry time
2800
+ * Useful to keep session active during user activity
2801
+ */
2802
+ extendCache(additionalTTL) {
2803
+ if (!this.sessionKey) {
2804
+ throw new Error("No session key loaded");
2805
+ }
2806
+ this.sessionKey.extendCache(additionalTTL);
2807
+ }
2808
+ /**
2809
+ * Get session key status
2810
+ */
2811
+ async getStatus(sessionKeyId) {
2812
+ const keyId = sessionKeyId || this.sessionKeyId;
2813
+ if (!keyId) {
2814
+ throw new Error("No session key ID provided");
2815
+ }
2816
+ return await this.request("POST", "/api/v1/ai/session-keys/status", {
2817
+ session_key_id: keyId
2818
+ });
2819
+ }
2820
+ // ============================================
2821
+ // Private HTTP Helper
2822
+ // ============================================
2823
+ async request(method, path, body, additionalHeaders) {
2824
+ const url = `${this.baseURL}${path}`;
2825
+ const headers = {
2826
+ "Content-Type": "application/json",
2827
+ "Authorization": `Bearer ${this.apiKey}`,
2828
+ ...additionalHeaders
2829
+ };
2830
+ const response = await fetch(url, {
2831
+ method,
2832
+ headers,
2833
+ body: body ? JSON.stringify(body) : void 0
2834
+ });
2835
+ if (!response.ok) {
2836
+ const error = await response.json().catch(() => ({ error: "Unknown error" }));
2837
+ throw new Error(`API Error: ${error.error || response.statusText}`);
2838
+ }
2839
+ return await response.json();
2840
+ }
2841
+ };
2842
+
2843
+ // src/lit-crypto-signer.ts
2844
+ var SPENDING_LIMIT_ACTION_CID = "QmXXunoMeNhXhnr4onzBuvnMzDqH8rf1qdM94RKXayypX3";
2845
+ var LitCryptoSigner = class {
2846
+ config;
2847
+ litNodeClient = null;
2848
+ connected = false;
2849
+ constructor(config = {}) {
2850
+ this.config = {
2851
+ network: config.network || "datil-dev",
2852
+ apiEndpoint: config.apiEndpoint || "https://api.zendfi.tech",
2853
+ apiKey: config.apiKey || "",
2854
+ debug: config.debug || false
2855
+ };
2856
+ }
2857
+ async connect() {
2858
+ if (this.connected && this.litNodeClient) {
2859
+ return;
2860
+ }
2861
+ this.log("Connecting to Lit Protocol network:", this.config.network);
2862
+ const { LitNodeClient } = await import("@lit-protocol/lit-node-client");
2863
+ this.litNodeClient = new LitNodeClient({
2864
+ litNetwork: this.config.network,
2865
+ debug: this.config.debug
2866
+ });
2867
+ await this.litNodeClient.connect();
2868
+ this.connected = true;
2869
+ this.log("Connected to Lit Protocol");
2870
+ }
2871
+ async disconnect() {
2872
+ if (this.litNodeClient) {
2873
+ await this.litNodeClient.disconnect();
2874
+ this.litNodeClient = null;
2875
+ this.connected = false;
2876
+ }
2877
+ }
2878
+ async signPayment(params) {
2879
+ if (!this.connected || !this.litNodeClient) {
2880
+ throw new Error("Not connected to Lit Protocol. Call connect() first.");
2881
+ }
2882
+ this.log("Signing payment with Lit Protocol");
2883
+ this.log(" Session:", params.sessionId);
2884
+ this.log(" Amount: $" + params.amountUsd);
539
2885
  try {
540
- const url = `${this.config.baseURL}${endpoint}`;
541
- const headers = {
542
- "Content-Type": "application/json",
543
- Authorization: `Bearer ${this.config.apiKey}`
544
- };
545
- if (idempotencyKey) {
546
- headers["Idempotency-Key"] = idempotencyKey;
547
- }
548
- const controller = new AbortController();
549
- const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
550
- const response = await fetch(url, {
551
- method,
552
- headers,
553
- body: data ? JSON.stringify(data) : void 0,
554
- signal: controller.signal
555
- });
556
- clearTimeout(timeoutId);
557
- let body;
558
- try {
559
- body = await response.json();
560
- } catch {
561
- body = null;
2886
+ let sessionSigs = params.sessionSigs;
2887
+ if (!sessionSigs) {
2888
+ sessionSigs = await this.getSessionSigs(params.pkpPublicKey);
562
2889
  }
563
- if (!response.ok) {
564
- const error = parseError(response, body);
565
- if (response.status >= 500 && attempt < this.config.retries) {
566
- const delay = Math.pow(2, attempt) * 1e3;
567
- await sleep(delay);
568
- return this.request(method, endpoint, data, {
569
- idempotencyKey,
570
- attempt: attempt + 1
571
- });
2890
+ const result = await this.litNodeClient.executeJs({
2891
+ ipfsId: SPENDING_LIMIT_ACTION_CID,
2892
+ sessionSigs,
2893
+ jsParams: {
2894
+ sessionId: params.sessionId,
2895
+ requestedAmountUsd: params.amountUsd,
2896
+ merchantId: params.merchantId,
2897
+ transactionToSign: params.transactionToSign,
2898
+ apiEndpoint: this.config.apiEndpoint,
2899
+ apiKey: this.config.apiKey,
2900
+ pkpPublicKey: params.pkpPublicKey
572
2901
  }
573
- throw error;
574
- }
575
- return body;
2902
+ });
2903
+ this.log("Lit Action result:", result);
2904
+ const response = JSON.parse(result.response);
2905
+ return {
2906
+ success: response.success,
2907
+ signature: response.signature,
2908
+ publicKey: response.publicKey,
2909
+ recid: response.recid,
2910
+ sessionId: response.session_id,
2911
+ amountUsd: response.amount_usd,
2912
+ remainingBudget: response.remaining_budget,
2913
+ cryptoEnforced: response.crypto_enforced ?? true,
2914
+ error: response.error,
2915
+ code: response.code,
2916
+ currentSpent: response.current_spent,
2917
+ limit: response.limit,
2918
+ remaining: response.remaining
2919
+ };
576
2920
  } catch (error) {
577
- if (error.name === "AbortError") {
578
- throw new Error(`Request timeout after ${this.config.timeout}ms`);
579
- }
580
- if (attempt < this.config.retries && error.message?.includes("fetch")) {
581
- const delay = Math.pow(2, attempt) * 1e3;
582
- await sleep(delay);
583
- return this.request(method, endpoint, data, {
584
- idempotencyKey,
585
- attempt: attempt + 1
586
- });
587
- }
588
- throw error;
2921
+ this.log("Lit signing error:", error);
2922
+ return {
2923
+ success: false,
2924
+ cryptoEnforced: true,
2925
+ error: error instanceof Error ? error.message : "Unknown error",
2926
+ code: "LIT_ERROR"
2927
+ };
589
2928
  }
590
2929
  }
591
- };
592
- var zendfi = (() => {
593
- try {
594
- return new ZendFiClient();
595
- } catch (error) {
596
- if (process.env.NODE_ENV === "test" || !process.env.ZENDFI_API_KEY) {
597
- return new Proxy({}, {
598
- get() {
599
- throw new Error(
600
- 'ZendFi singleton not initialized. Set ZENDFI_API_KEY environment variable or create a custom instance: new ZendFiClient({ apiKey: "..." })'
601
- );
2930
+ async getSessionSigs(pkpPublicKey) {
2931
+ this.log("Generating Lit session signatures");
2932
+ if (typeof window !== "undefined" && window.ethereum) {
2933
+ const { ethers } = await import("ethers");
2934
+ const provider = new ethers.BrowserProvider(window.ethereum);
2935
+ const signer = await provider.getSigner();
2936
+ const { LitAbility, LitPKPResource } = await import("@lit-protocol/auth-helpers");
2937
+ const sessionSigs = await this.litNodeClient.getSessionSigs({
2938
+ pkpPublicKey,
2939
+ chain: "ethereum",
2940
+ expiration: new Date(Date.now() + 1e3 * 60 * 10).toISOString(),
2941
+ resourceAbilityRequests: [
2942
+ {
2943
+ resource: new LitPKPResource("*"),
2944
+ ability: LitAbility.PKPSigning
2945
+ }
2946
+ ],
2947
+ authNeededCallback: async (params) => {
2948
+ const message = params.message;
2949
+ const signature = await signer.signMessage(message);
2950
+ return {
2951
+ sig: signature,
2952
+ derivedVia: "web3.eth.personal.sign",
2953
+ signedMessage: message,
2954
+ address: await signer.getAddress()
2955
+ };
602
2956
  }
603
2957
  });
2958
+ return sessionSigs;
604
2959
  }
605
- throw error;
2960
+ throw new Error(
2961
+ "No wallet available. In browser, ensure MetaMask or similar is connected. In Node.js, pass pre-generated sessionSigs to signPayment()."
2962
+ );
606
2963
  }
607
- })();
608
-
609
- // src/webhooks.ts
610
- async function verifyNextWebhook(request, secret) {
611
- try {
612
- const payload = await request.text();
613
- const signature = request.headers.get("x-zendfi-signature");
614
- if (!signature) {
615
- return null;
616
- }
617
- const webhookSecret = secret || process.env.ZENDFI_WEBHOOK_SECRET;
618
- if (!webhookSecret) {
619
- throw new Error("ZENDFI_WEBHOOK_SECRET not configured");
620
- }
621
- const isValid = zendfi.verifyWebhook({
622
- payload,
623
- signature,
624
- secret: webhookSecret
625
- });
626
- if (!isValid) {
627
- return null;
2964
+ log(...args) {
2965
+ if (this.config.debug) {
2966
+ console.log("[LitCryptoSigner]", ...args);
628
2967
  }
629
- return JSON.parse(payload);
630
- } catch {
631
- return null;
632
2968
  }
2969
+ };
2970
+ function requiresLitSigning(session) {
2971
+ return session.crypto_enforced === true || session.mint_pkp === true;
633
2972
  }
634
- async function verifyExpressWebhook(request, secret) {
635
- try {
636
- const payload = request.rawBody || JSON.stringify(request.body);
637
- const signature = request.headers["x-zendfi-signature"];
638
- if (!signature) {
639
- return null;
640
- }
641
- const webhookSecret = secret || process.env.ZENDFI_WEBHOOK_SECRET;
642
- if (!webhookSecret) {
643
- throw new Error("ZENDFI_WEBHOOK_SECRET not configured");
644
- }
645
- const isValid = zendfi.verifyWebhook({
646
- payload,
647
- signature,
648
- secret: webhookSecret
649
- });
650
- if (!isValid) {
651
- return null;
652
- }
653
- return JSON.parse(payload);
654
- } catch {
2973
+ function encodeTransactionForLit(transaction) {
2974
+ const bytes = transaction instanceof Uint8Array ? transaction : new Uint8Array(transaction);
2975
+ return btoa(String.fromCharCode(...bytes));
2976
+ }
2977
+ function decodeSignatureFromLit(result) {
2978
+ if (!result.success || !result.signature) {
655
2979
  return null;
656
2980
  }
657
- }
658
- function verifyWebhookSignature(payload, signature, secret) {
659
- return zendfi.verifyWebhook({
660
- payload,
661
- signature,
662
- secret
663
- });
2981
+ const hex = result.signature.startsWith("0x") ? result.signature.slice(2) : result.signature;
2982
+ const bytes = new Uint8Array(hex.length / 2);
2983
+ for (let i = 0; i < hex.length; i += 2) {
2984
+ bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
2985
+ }
2986
+ return bytes;
664
2987
  }
665
2988
  export {
666
- AuthenticationError,
2989
+ AgentAPI,
2990
+ ApiError,
2991
+ AuthenticationError2 as AuthenticationError,
2992
+ AutonomyAPI,
667
2993
  ConfigLoader,
668
- NetworkError,
669
- RateLimitError,
670
- ValidationError,
2994
+ DeviceBoundSessionKey,
2995
+ DeviceFingerprintGenerator,
2996
+ ERROR_CODES,
2997
+ InterceptorManager,
2998
+ LitCryptoSigner,
2999
+ NetworkError2 as NetworkError,
3000
+ PaymentError,
3001
+ PaymentIntentsAPI,
3002
+ PricingAPI,
3003
+ RateLimitError2 as RateLimitError,
3004
+ RateLimiter,
3005
+ RecoveryQRGenerator,
3006
+ SPENDING_LIMIT_ACTION_CID,
3007
+ SessionKeyCrypto,
3008
+ SmartPaymentsAPI,
3009
+ ValidationError2 as ValidationError,
3010
+ WebhookError,
671
3011
  ZendFiClient,
672
- ZendFiError,
3012
+ ZendFiError2 as ZendFiError,
3013
+ ZendFiSessionKeyManager,
3014
+ asAgentKeyId,
3015
+ asEscrowId,
3016
+ asInstallmentPlanId,
3017
+ asIntentId,
3018
+ asInvoiceId,
3019
+ asMerchantId,
3020
+ asPaymentId,
3021
+ asPaymentLinkCode,
3022
+ asSessionId,
3023
+ asSubscriptionId,
3024
+ createZendFiError,
3025
+ decodeSignatureFromLit,
3026
+ encodeTransactionForLit,
3027
+ generateIdempotencyKey,
3028
+ isZendFiError,
673
3029
  processWebhook,
3030
+ requiresLitSigning,
3031
+ sleep,
674
3032
  verifyExpressWebhook,
675
3033
  verifyNextWebhook,
676
3034
  verifyWebhookSignature,