@zendfi/sdk 0.4.0 → 0.5.0

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