@zendfi/sdk 0.4.0 → 0.5.1

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