@zendfi/sdk 0.8.4 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,9 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __esm = (fn, res) => function __init() {
9
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
- };
11
8
  var __export = (target, all) => {
12
9
  for (var name in all)
13
10
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -30,417 +27,20 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
27
  ));
31
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
29
 
33
- // src/helpers/cache.ts
34
- var cache_exports = {};
35
- __export(cache_exports, {
36
- QuickCaches: () => QuickCaches,
37
- SessionKeyCache: () => SessionKeyCache
38
- });
39
- var SessionKeyCache, QuickCaches;
40
- var init_cache = __esm({
41
- "src/helpers/cache.ts"() {
42
- "use strict";
43
- SessionKeyCache = class {
44
- memoryCache = /* @__PURE__ */ new Map();
45
- config;
46
- refreshTimers = /* @__PURE__ */ new Map();
47
- constructor(config = {}) {
48
- this.config = {
49
- storage: config.storage || "memory",
50
- ttl: config.ttl || 30 * 60 * 1e3,
51
- // 30 minutes default
52
- autoRefresh: config.autoRefresh || false,
53
- namespace: config.namespace || "zendfi_cache",
54
- debug: config.debug || false
55
- };
56
- }
57
- /**
58
- * Get cached keypair or decrypt and cache
59
- */
60
- async getCached(sessionKeyId, decryptFn, options) {
61
- this.log(`getCached: ${sessionKeyId}`);
62
- const memoryCached = this.memoryCache.get(sessionKeyId);
63
- if (memoryCached && Date.now() < memoryCached.expiry) {
64
- this.log(`Memory cache HIT: ${sessionKeyId}`);
65
- return memoryCached.keypair;
66
- }
67
- if (this.config.storage !== "memory") {
68
- const persistentCached = await this.getFromStorage(sessionKeyId);
69
- if (persistentCached && Date.now() < persistentCached.expiry) {
70
- if (options?.deviceFingerprint && persistentCached.deviceFingerprint) {
71
- if (options.deviceFingerprint !== persistentCached.deviceFingerprint) {
72
- this.log(`Device fingerprint mismatch for ${sessionKeyId}`);
73
- await this.invalidate(sessionKeyId);
74
- return await this.decryptAndCache(sessionKeyId, decryptFn, options);
75
- }
76
- }
77
- this.log(`Persistent cache HIT: ${sessionKeyId}`);
78
- this.memoryCache.set(sessionKeyId, persistentCached);
79
- return persistentCached.keypair;
80
- }
81
- }
82
- this.log(`Cache MISS: ${sessionKeyId}`);
83
- return await this.decryptAndCache(sessionKeyId, decryptFn, options);
84
- }
85
- /**
86
- * Decrypt keypair and cache it
87
- */
88
- async decryptAndCache(sessionKeyId, decryptFn, options) {
89
- const keypair = await decryptFn();
90
- const expiry = Date.now() + this.config.ttl;
91
- const cached = {
92
- keypair,
93
- expiry,
94
- sessionKeyId,
95
- deviceFingerprint: options?.deviceFingerprint
96
- };
97
- this.memoryCache.set(sessionKeyId, cached);
98
- if (this.config.storage !== "memory") {
99
- await this.setInStorage(sessionKeyId, cached);
100
- }
101
- if (this.config.autoRefresh) {
102
- this.setupAutoRefresh(sessionKeyId, decryptFn, options);
103
- }
104
- this.log(`Cached: ${sessionKeyId}, expires in ${this.config.ttl}ms`);
105
- return keypair;
106
- }
107
- /**
108
- * Invalidate cached keypair
109
- */
110
- async invalidate(sessionKeyId) {
111
- this.log(`Invalidating: ${sessionKeyId}`);
112
- this.memoryCache.delete(sessionKeyId);
113
- const timer = this.refreshTimers.get(sessionKeyId);
114
- if (timer) {
115
- clearTimeout(timer);
116
- this.refreshTimers.delete(sessionKeyId);
117
- }
118
- if (this.config.storage !== "memory") {
119
- await this.removeFromStorage(sessionKeyId);
120
- }
121
- }
122
- /**
123
- * Clear all cached keypairs
124
- */
125
- async clear() {
126
- this.log("Clearing all cache");
127
- this.memoryCache.clear();
128
- for (const timer of this.refreshTimers.values()) {
129
- clearTimeout(timer);
130
- }
131
- this.refreshTimers.clear();
132
- if (this.config.storage !== "memory") {
133
- await this.clearStorage();
134
- }
135
- }
136
- /**
137
- * Get cache statistics
138
- */
139
- getStats() {
140
- const entries = Array.from(this.memoryCache.entries()).map(([id, cached]) => ({
141
- sessionKeyId: id,
142
- expiresIn: Math.max(0, cached.expiry - Date.now())
143
- }));
144
- return { size: this.memoryCache.size, entries };
145
- }
146
- /**
147
- * Check if a session key is cached and valid
148
- */
149
- isCached(sessionKeyId) {
150
- const cached = this.memoryCache.get(sessionKeyId);
151
- return cached ? Date.now() < cached.expiry : false;
152
- }
153
- /**
154
- * Update TTL for a cached session key
155
- */
156
- async extendTTL(sessionKeyId, additionalMs) {
157
- const cached = this.memoryCache.get(sessionKeyId);
158
- if (!cached) return false;
159
- cached.expiry += additionalMs;
160
- this.memoryCache.set(sessionKeyId, cached);
161
- if (this.config.storage !== "memory") {
162
- await this.setInStorage(sessionKeyId, cached);
163
- }
164
- this.log(`Extended TTL for ${sessionKeyId} by ${additionalMs}ms`);
165
- return true;
166
- }
167
- // ============================================
168
- // Storage Backend Implementations
169
- // ============================================
170
- async getFromStorage(sessionKeyId) {
171
- try {
172
- const key = this.getStorageKey(sessionKeyId);
173
- if (this.config.storage === "localStorage") {
174
- const data = localStorage.getItem(key);
175
- if (!data) return null;
176
- const parsed = JSON.parse(data);
177
- return {
178
- ...parsed,
179
- keypair: this.deserializeKeypair(parsed.keypair)
180
- };
181
- }
182
- if (this.config.storage === "indexedDB") {
183
- return await this.getFromIndexedDB(key);
184
- }
185
- if (typeof this.config.storage === "object") {
186
- const data = await this.config.storage.get(key);
187
- if (!data) return null;
188
- const parsed = JSON.parse(data);
189
- return {
190
- ...parsed,
191
- keypair: this.deserializeKeypair(parsed.keypair)
192
- };
193
- }
194
- } catch (error) {
195
- this.log(`Error reading from storage: ${error}`);
196
- }
197
- return null;
198
- }
199
- async setInStorage(sessionKeyId, cached) {
200
- try {
201
- const key = this.getStorageKey(sessionKeyId);
202
- const serialized = {
203
- ...cached,
204
- keypair: this.serializeKeypair(cached.keypair)
205
- };
206
- if (this.config.storage === "localStorage") {
207
- localStorage.setItem(key, JSON.stringify(serialized));
208
- return;
209
- }
210
- if (this.config.storage === "indexedDB") {
211
- await this.setInIndexedDB(key, serialized);
212
- return;
213
- }
214
- if (typeof this.config.storage === "object") {
215
- await this.config.storage.set(key, JSON.stringify(serialized));
216
- }
217
- } catch (error) {
218
- this.log(`Error writing to storage: ${error}`);
219
- }
220
- }
221
- async removeFromStorage(sessionKeyId) {
222
- try {
223
- const key = this.getStorageKey(sessionKeyId);
224
- if (this.config.storage === "localStorage") {
225
- localStorage.removeItem(key);
226
- return;
227
- }
228
- if (this.config.storage === "indexedDB") {
229
- await this.removeFromIndexedDB(key);
230
- return;
231
- }
232
- if (typeof this.config.storage === "object") {
233
- await this.config.storage.remove(key);
234
- }
235
- } catch (error) {
236
- this.log(`Error removing from storage: ${error}`);
237
- }
238
- }
239
- async clearStorage() {
240
- try {
241
- if (this.config.storage === "localStorage") {
242
- const keysToRemove = [];
243
- for (let i = 0; i < localStorage.length; i++) {
244
- const key = localStorage.key(i);
245
- if (key?.startsWith(this.config.namespace)) {
246
- keysToRemove.push(key);
247
- }
248
- }
249
- keysToRemove.forEach((key) => localStorage.removeItem(key));
250
- return;
251
- }
252
- if (this.config.storage === "indexedDB") {
253
- await this.clearIndexedDB();
254
- return;
255
- }
256
- if (typeof this.config.storage === "object") {
257
- await this.config.storage.clear();
258
- }
259
- } catch (error) {
260
- this.log(`Error clearing storage: ${error}`);
261
- }
262
- }
263
- // ============================================
264
- // IndexedDB Helpers
265
- // ============================================
266
- async getFromIndexedDB(key) {
267
- return new Promise((resolve) => {
268
- const request = indexedDB.open(this.config.namespace, 1);
269
- request.onerror = () => resolve(null);
270
- request.onupgradeneeded = (event) => {
271
- const db = event.target.result;
272
- if (!db.objectStoreNames.contains("cache")) {
273
- db.createObjectStore("cache");
274
- }
275
- };
276
- request.onsuccess = (event) => {
277
- const db = event.target.result;
278
- const transaction = db.transaction(["cache"], "readonly");
279
- const store = transaction.objectStore("cache");
280
- const getRequest = store.get(key);
281
- getRequest.onsuccess = () => {
282
- resolve(getRequest.result || null);
283
- };
284
- getRequest.onerror = () => resolve(null);
285
- };
286
- });
287
- }
288
- async setInIndexedDB(key, value) {
289
- return new Promise((resolve, reject) => {
290
- const request = indexedDB.open(this.config.namespace, 1);
291
- request.onerror = () => reject(new Error("IndexedDB error"));
292
- request.onupgradeneeded = (event) => {
293
- const db = event.target.result;
294
- if (!db.objectStoreNames.contains("cache")) {
295
- db.createObjectStore("cache");
296
- }
297
- };
298
- request.onsuccess = (event) => {
299
- const db = event.target.result;
300
- const transaction = db.transaction(["cache"], "readwrite");
301
- const store = transaction.objectStore("cache");
302
- store.put(value, key);
303
- transaction.oncomplete = () => resolve();
304
- transaction.onerror = () => reject(new Error("IndexedDB transaction error"));
305
- };
306
- });
307
- }
308
- async removeFromIndexedDB(key) {
309
- return new Promise((resolve) => {
310
- const request = indexedDB.open(this.config.namespace, 1);
311
- request.onsuccess = (event) => {
312
- const db = event.target.result;
313
- const transaction = db.transaction(["cache"], "readwrite");
314
- const store = transaction.objectStore("cache");
315
- store.delete(key);
316
- transaction.oncomplete = () => resolve();
317
- };
318
- request.onerror = () => resolve();
319
- });
320
- }
321
- async clearIndexedDB() {
322
- return new Promise((resolve) => {
323
- const request = indexedDB.open(this.config.namespace, 1);
324
- request.onsuccess = (event) => {
325
- const db = event.target.result;
326
- const transaction = db.transaction(["cache"], "readwrite");
327
- const store = transaction.objectStore("cache");
328
- store.clear();
329
- transaction.oncomplete = () => resolve();
330
- };
331
- request.onerror = () => resolve();
332
- });
333
- }
334
- // ============================================
335
- // Serialization
336
- // ============================================
337
- serializeKeypair(keypair) {
338
- if (keypair && typeof keypair === "object" && "secretKey" in keypair) {
339
- return {
340
- type: "solana",
341
- secretKey: Array.from(keypair.secretKey)
342
- };
343
- }
344
- if (keypair instanceof Uint8Array) {
345
- return {
346
- type: "uint8array",
347
- data: Array.from(keypair)
348
- };
349
- }
350
- return keypair;
351
- }
352
- deserializeKeypair(data) {
353
- if (!data || typeof data !== "object") return data;
354
- if (data.type === "solana" && data.secretKey) {
355
- return new Uint8Array(data.secretKey);
356
- }
357
- if (data.type === "uint8array" && data.data) {
358
- return new Uint8Array(data.data);
359
- }
360
- return data;
361
- }
362
- // ============================================
363
- // Auto-Refresh
364
- // ============================================
365
- setupAutoRefresh(sessionKeyId, decryptFn, options) {
366
- const existingTimer = this.refreshTimers.get(sessionKeyId);
367
- if (existingTimer) {
368
- clearTimeout(existingTimer);
369
- }
370
- const refreshIn = Math.max(0, this.config.ttl - 5 * 60 * 1e3);
371
- const timer = setTimeout(async () => {
372
- this.log(`Auto-refreshing: ${sessionKeyId}`);
373
- try {
374
- await this.decryptAndCache(sessionKeyId, decryptFn, options);
375
- } catch (error) {
376
- this.log(`Auto-refresh failed: ${error}`);
377
- }
378
- }, refreshIn);
379
- this.refreshTimers.set(sessionKeyId, timer);
380
- }
381
- // ============================================
382
- // Utilities
383
- // ============================================
384
- getStorageKey(sessionKeyId) {
385
- return `${this.config.namespace}:${sessionKeyId}`;
386
- }
387
- log(message) {
388
- if (this.config.debug) {
389
- console.log(`[SessionKeyCache] ${message}`);
390
- }
391
- }
392
- };
393
- QuickCaches = {
394
- /** Memory-only cache (30 minutes) */
395
- memory: () => new SessionKeyCache({ storage: "memory", ttl: 30 * 60 * 1e3 }),
396
- /** Persistent cache (1 hour, survives reload) */
397
- persistent: () => new SessionKeyCache({ storage: "localStorage", ttl: 60 * 60 * 1e3 }),
398
- /** Long-term cache (24 hours, IndexedDB) */
399
- longTerm: () => new SessionKeyCache({ storage: "indexedDB", ttl: 24 * 60 * 60 * 1e3, autoRefresh: true }),
400
- /** Secure cache (5 minutes, memory-only) */
401
- secure: () => new SessionKeyCache({ storage: "memory", ttl: 5 * 60 * 1e3 })
402
- };
403
- }
404
- });
405
-
406
30
  // src/index.ts
407
31
  var index_exports = {};
408
32
  __export(index_exports, {
409
- AgentAPI: () => AgentAPI,
410
- AnthropicAdapter: () => AnthropicAdapter,
411
33
  ApiError: () => ApiError,
412
34
  AuthenticationError: () => AuthenticationError2,
413
- AutonomyAPI: () => AutonomyAPI,
414
35
  ConfigLoader: () => ConfigLoader,
415
36
  DevTools: () => DevTools,
416
- DeviceBoundSessionKey: () => DeviceBoundSessionKey,
417
- DeviceFingerprintGenerator: () => DeviceFingerprintGenerator,
418
37
  ERROR_CODES: () => ERROR_CODES,
419
- ErrorRecovery: () => ErrorRecovery,
420
- GeminiAdapter: () => GeminiAdapter,
421
38
  InterceptorManager: () => InterceptorManager,
422
- LitCryptoSigner: () => LitCryptoSigner,
423
39
  NetworkError: () => NetworkError2,
424
- OpenAIAdapter: () => OpenAIAdapter,
425
- PINRateLimiter: () => PINRateLimiter,
426
- PINValidator: () => PINValidator,
427
40
  PaymentError: () => PaymentError,
428
- PaymentIntentParser: () => PaymentIntentParser,
429
- PaymentIntentsAPI: () => PaymentIntentsAPI,
430
41
  PerformanceMonitor: () => PerformanceMonitor,
431
- PricingAPI: () => PricingAPI,
432
- QuickCaches: () => QuickCaches,
433
42
  RateLimitError: () => RateLimitError2,
434
43
  RateLimiter: () => RateLimiter,
435
- RecoveryQRGenerator: () => RecoveryQRGenerator,
436
- RetryStrategy: () => RetryStrategy,
437
- SPENDING_LIMIT_ACTION_CID: () => SPENDING_LIMIT_ACTION_CID,
438
- SecureStorage: () => SecureStorage,
439
- SessionKeyCache: () => SessionKeyCache,
440
- SessionKeyCrypto: () => SessionKeyCrypto,
441
- SessionKeyLifecycle: () => SessionKeyLifecycle,
442
- SessionKeysAPI: () => SessionKeysAPI,
443
- SmartPaymentsAPI: () => SmartPaymentsAPI,
444
44
  TransactionMonitor: () => TransactionMonitor,
445
45
  TransactionPoller: () => TransactionPoller,
446
46
  ValidationError: () => ValidationError2,
@@ -449,25 +49,17 @@ __export(index_exports, {
449
49
  ZendFiClient: () => ZendFiClient,
450
50
  ZendFiEmbeddedCheckout: () => ZendFiEmbeddedCheckout,
451
51
  ZendFiError: () => ZendFiError2,
452
- asAgentKeyId: () => asAgentKeyId,
453
- asEscrowId: () => asEscrowId,
454
52
  asInstallmentPlanId: () => asInstallmentPlanId,
455
- asIntentId: () => asIntentId,
456
53
  asInvoiceId: () => asInvoiceId,
457
54
  asMerchantId: () => asMerchantId,
458
55
  asPaymentId: () => asPaymentId,
459
56
  asPaymentLinkCode: () => asPaymentLinkCode,
460
- asSessionId: () => asSessionId,
461
57
  asSubscriptionId: () => asSubscriptionId,
462
58
  createWalletHook: () => createWalletHook,
463
59
  createZendFiError: () => createZendFiError,
464
- decodeSignatureFromLit: () => decodeSignatureFromLit,
465
- encodeTransactionForLit: () => encodeTransactionForLit,
466
60
  generateIdempotencyKey: () => generateIdempotencyKey,
467
61
  isZendFiError: () => isZendFiError,
468
62
  processWebhook: () => processWebhook,
469
- requiresLitSigning: () => requiresLitSigning,
470
- setupQuickSessionKey: () => setupQuickSessionKey,
471
63
  sleep: () => sleep,
472
64
  verifyExpressWebhook: () => verifyExpressWebhook,
473
65
  verifyNextWebhook: () => verifyNextWebhook,
@@ -482,15 +74,11 @@ var import_crypto = require("crypto");
482
74
 
483
75
  // src/types.ts
484
76
  var asPaymentId = (id) => id;
485
- var asSessionId = (id) => id;
486
- var asAgentKeyId = (id) => id;
487
77
  var asMerchantId = (id) => id;
488
78
  var asInvoiceId = (id) => id;
489
79
  var asSubscriptionId = (id) => id;
490
- var asEscrowId = (id) => id;
491
80
  var asInstallmentPlanId = (id) => id;
492
81
  var asPaymentLinkCode = (id) => id;
493
- var asIntentId = (id) => id;
494
82
 
495
83
  // src/utils.ts
496
84
  var ConfigLoader = class {
@@ -931,2257 +519,133 @@ function createInterceptors() {
931
519
  };
932
520
  }
933
521
 
934
- // src/aip/agent.ts
935
- function normalizeArrayResponse(response, key) {
936
- if (Array.isArray(response)) {
937
- return response;
938
- }
939
- return response[key] || [];
940
- }
941
- var AgentAPI = class {
942
- constructor(request) {
943
- this.request = request;
522
+ // src/client.ts
523
+ var ZendFiClient = class {
524
+ config;
525
+ interceptors;
526
+ constructor(options) {
527
+ this.config = ConfigLoader.load(options);
528
+ ConfigLoader.validateApiKey(this.config.apiKey);
529
+ this.interceptors = createInterceptors();
530
+ if (this.config.environment === "development" || this.config.debug) {
531
+ console.log(
532
+ `\u2713 ZendFi SDK initialized in ${this.config.mode} mode (${this.config.mode === "test" ? "devnet" : "mainnet"})`
533
+ );
534
+ if (this.config.debug) {
535
+ console.log("[ZendFi] Debug mode enabled");
536
+ }
537
+ }
944
538
  }
945
- // ============================================
946
- // Agent API Keys
947
- // ============================================
948
539
  /**
949
- * Create a new agent API key with scoped permissions
950
- *
951
- * Agent keys (prefixed with `zai_`) have limited permissions compared to
952
- * merchant keys. This enables safe delegation to AI agents.
953
- *
954
- * @param request - Agent key configuration
955
- * @returns The created agent key (full_key only returned on creation!)
956
- *
957
- * @example
958
- * ```typescript
959
- * const agentKey = await zendfi.agent.createKey({
960
- * name: 'Shopping Assistant',
961
- * agent_id: 'shopping-assistant-v1',
962
- * scopes: ['create_payments'],
963
- * rate_limit_per_hour: 500,
964
- * });
965
- *
966
- * // IMPORTANT: Save the full_key now - it won't be shown again!
967
- * console.log(agentKey.full_key); // => "zai_test_abc123..."
968
- * ```
540
+ * Create a new payment
969
541
  */
970
- async createKey(request) {
971
- return this.request("POST", "/api/v1/agent-keys", {
972
- name: request.name,
973
- agent_id: request.agent_id,
974
- agent_name: request.agent_name,
975
- scopes: request.scopes || ["create_payments"],
976
- rate_limit_per_hour: request.rate_limit_per_hour || 1e3,
977
- metadata: request.metadata
542
+ async createPayment(request) {
543
+ return this.request("POST", "/api/v1/payments", {
544
+ ...request,
545
+ currency: request.currency || "USD",
546
+ token: request.token || "USDC"
978
547
  });
979
548
  }
980
549
  /**
981
- * List all agent API keys for the merchant
982
- *
983
- * @returns Array of agent API keys (without full_key for security)
984
- *
985
- * @example
986
- * ```typescript
987
- * const keys = await zendfi.agent.listKeys();
988
- * keys.forEach(key => {
989
- * console.log(`${key.name}: ${key.key_prefix}*** (${key.scopes.join(', ')})`);
990
- * });
991
- * ```
550
+ * Get payment by ID
992
551
  */
993
- async listKeys() {
994
- const response = await this.request(
995
- "GET",
996
- "/api/v1/agent-keys"
997
- );
998
- return normalizeArrayResponse(response, "keys");
552
+ async getPayment(paymentId) {
553
+ return this.request("GET", `/api/v1/payments/${paymentId}`);
999
554
  }
1000
555
  /**
1001
- * Revoke an agent API key
1002
- *
1003
- * Once revoked, the key cannot be used for any API calls.
1004
- * This action is irreversible.
1005
- *
1006
- * @param keyId - UUID of the agent key to revoke
1007
- *
1008
- * @example
1009
- * ```typescript
1010
- * await zendfi.agent.revokeKey('ak_123...');
1011
- * console.log('Agent key revoked');
1012
- * ```
556
+ * Create a subscription plan
1013
557
  */
1014
- async revokeKey(keyId) {
1015
- await this.request("POST", `/api/v1/agent-keys/${keyId}/revoke`);
558
+ async createSubscriptionPlan(request) {
559
+ return this.request("POST", "/api/v1/subscriptions/plans", {
560
+ ...request,
561
+ currency: request.currency || "USD",
562
+ interval_count: request.interval_count || 1,
563
+ trial_days: request.trial_days || 0
564
+ });
1016
565
  }
1017
- // ============================================
1018
- // Agent Sessions
1019
- // ============================================
1020
566
  /**
1021
- * Create an agent session with spending limits
1022
- *
1023
- * Sessions provide time-bounded authorization for agents to make payments
1024
- * on behalf of users, with configurable spending limits.
1025
- *
1026
- * @param request - Session configuration
1027
- * @returns The created session with token
1028
- *
1029
- * @example
1030
- * ```typescript
1031
- * const session = await zendfi.agent.createSession({
1032
- * agent_id: 'shopping-assistant-v1',
1033
- * agent_name: 'Shopping Assistant',
1034
- * user_wallet: 'Hx7B...abc',
1035
- * limits: {
1036
- * max_per_transaction: 100,
1037
- * max_per_day: 500,
1038
- * require_approval_above: 50,
1039
- * },
1040
- * duration_hours: 24,
1041
- * });
1042
- *
1043
- * // Use session_token for subsequent API calls
1044
- * console.log(session.session_token); // => "zai_session_..."
1045
- * ```
567
+ * Get subscription plan by ID
1046
568
  */
1047
- async createSession(request) {
1048
- return this.request("POST", "/api/v1/ai/sessions", {
1049
- agent_id: request.agent_id,
1050
- agent_name: request.agent_name,
1051
- user_wallet: request.user_wallet,
1052
- limits: request.limits || {
1053
- max_per_transaction: 1e3,
1054
- max_per_day: 5e3,
1055
- max_per_week: 2e4,
1056
- max_per_month: 5e4,
1057
- require_approval_above: 500
1058
- },
1059
- allowed_merchants: request.allowed_merchants,
1060
- duration_hours: request.duration_hours || 24,
1061
- mint_pkp: request.mint_pkp,
1062
- metadata: request.metadata
1063
- });
569
+ async getSubscriptionPlan(planId) {
570
+ return this.request("GET", `/api/v1/subscriptions/plans/${planId}`);
1064
571
  }
1065
572
  /**
1066
- * List all agent sessions
1067
- *
1068
- * @returns Array of agent sessions (both active and expired)
1069
- *
1070
- * @example
1071
- * ```typescript
1072
- * const sessions = await zendfi.agent.listSessions();
1073
- * const activeSessions = sessions.filter(s => s.is_active);
1074
- * console.log(`${activeSessions.length} active sessions`);
1075
- * ```
573
+ * Create a subscription
1076
574
  */
1077
- async listSessions() {
1078
- const response = await this.request(
1079
- "GET",
1080
- "/api/v1/ai/sessions"
1081
- );
1082
- return normalizeArrayResponse(response, "sessions");
575
+ async createSubscription(request) {
576
+ return this.request("POST", "/api/v1/subscriptions", request);
1083
577
  }
1084
578
  /**
1085
- * Get a specific agent session by ID
1086
- *
1087
- * @param sessionId - UUID of the session
1088
- * @returns The session details with remaining limits
1089
- *
1090
- * @example
1091
- * ```typescript
1092
- * const session = await zendfi.agent.getSession('sess_123...');
1093
- * console.log(`Remaining today: $${session.remaining_today}`);
1094
- * console.log(`Expires: ${session.expires_at}`);
1095
- * ```
579
+ * Get subscription by ID
1096
580
  */
1097
- async getSession(sessionId) {
1098
- return this.request("GET", `/api/v1/ai/sessions/${sessionId}`);
581
+ async getSubscription(subscriptionId) {
582
+ return this.request("GET", `/api/v1/subscriptions/${subscriptionId}`);
1099
583
  }
1100
584
  /**
1101
- * Revoke an agent session
1102
- *
1103
- * Immediately invalidates the session, preventing any further payments.
1104
- * This action is irreversible.
1105
- *
1106
- * @param sessionId - UUID of the session to revoke
1107
- *
1108
- * @example
1109
- * ```typescript
1110
- * await zendfi.agent.revokeSession('sess_123...');
1111
- * console.log('Session revoked - agent can no longer make payments');
1112
- * ```
585
+ * Cancel a subscription
1113
586
  */
1114
- async revokeSession(sessionId) {
1115
- await this.request("POST", `/api/v1/ai/sessions/${sessionId}/revoke`);
587
+ async cancelSubscription(subscriptionId) {
588
+ return this.request(
589
+ "POST",
590
+ `/api/v1/subscriptions/${subscriptionId}/cancel`
591
+ );
1116
592
  }
1117
- // ============================================
1118
- // Agent Payments
1119
- // ============================================
1120
593
  /**
1121
- * Execute a payment using an agent session
1122
- *
1123
- * This is the primary method for AI agents to make payments. It uses the
1124
- * session token to enforce spending limits and automatically routes the
1125
- * payment through the optimal path.
1126
- *
1127
- * The session token comes from `createSession()` and enforces:
1128
- * - Per-transaction limits
1129
- * - Daily limits
1130
- * - Weekly limits
1131
- * - Monthly limits
1132
- *
1133
- * @param request - Payment request with session token
1134
- * @returns Payment result with status and receipt
1135
- *
1136
- * @example
1137
- * ```typescript
1138
- * // Create a session first
1139
- * const session = await zendfi.agent.createSession({
1140
- * agent_id: 'shopping-bot',
1141
- * user_wallet: 'Hx7B...abc',
1142
- * limits: {
1143
- * max_per_transaction: 50,
1144
- * max_per_day: 200,
1145
- * },
1146
- * duration_hours: 24,
1147
- * });
1148
- *
1149
- * // Make payments within the session limits
1150
- * const payment = await zendfi.agent.pay({
1151
- * session_token: session.session_token,
1152
- * amount: 29.99,
1153
- * description: 'Premium widget',
1154
- * auto_gasless: true,
1155
- * });
1156
- *
1157
- * if (payment.requires_signature) {
1158
- * // Device-bound: user must sign
1159
- * console.log('Sign and submit to:', payment.submit_url);
1160
- * } else {
1161
- * // Auto-signed: payment complete
1162
- * console.log('Payment confirmed:', payment.transaction_signature);
1163
- * }
1164
- * ```
594
+ * Create a payment link (shareable checkout URL)
1165
595
  */
1166
- async pay(request) {
1167
- return this.request("POST", "/api/v1/ai/smart-payment", {
1168
- session_token: request.session_token,
1169
- agent_id: "session",
1170
- // Session already has agent_id bound
1171
- user_wallet: "",
1172
- // Will be extracted from session
1173
- amount_usd: request.amount,
1174
- merchant_id: request.recipient_merchant_id,
596
+ async createPaymentLink(request) {
597
+ const response = await this.request("POST", "/api/v1/payment-links", {
598
+ ...request,
599
+ currency: request.currency || "USD",
1175
600
  token: request.token || "USDC",
1176
- auto_detect_gasless: request.auto_gasless,
1177
- description: request.description,
1178
- metadata: request.metadata
601
+ onramp: request.onramp || false
1179
602
  });
603
+ return {
604
+ ...response,
605
+ url: response.hosted_page_url
606
+ };
1180
607
  }
1181
- // ============================================
1182
- // Agent Analytics
1183
- // ============================================
1184
608
  /**
1185
- * Get analytics for all agent activity
1186
- *
1187
- * @returns Comprehensive analytics including payments, success rate, and PPP savings
1188
- *
1189
- * @example
1190
- * ```typescript
1191
- * const analytics = await zendfi.agent.getAnalytics();
1192
- * console.log(`Total volume: $${analytics.total_volume_usd}`);
1193
- * console.log(`Success rate: ${(analytics.success_rate * 100).toFixed(1)}%`);
1194
- * console.log(`PPP savings: $${analytics.ppp_savings_usd}`);
1195
- * ```
609
+ * Get payment link by link code
1196
610
  */
1197
- async getAnalytics() {
1198
- return this.request("GET", "/api/v1/analytics/agents");
611
+ async getPaymentLink(linkCode) {
612
+ const response = await this.request("GET", `/api/v1/payment-links/${linkCode}`);
613
+ return {
614
+ ...response,
615
+ url: response.hosted_page_url
616
+ };
1199
617
  }
1200
- };
1201
-
1202
- // src/aip/intents.ts
1203
- var PaymentIntentsAPI = class {
1204
- constructor(request) {
1205
- this.request = request;
618
+ /**
619
+ * List all payment links for the authenticated merchant
620
+ */
621
+ async listPaymentLinks() {
622
+ const response = await this.request("GET", "/api/v1/payment-links");
623
+ return response.map((link) => ({
624
+ ...link,
625
+ url: link.hosted_page_url
626
+ }));
1206
627
  }
1207
628
  /**
1208
- * Create a payment intent
1209
- *
1210
- * This is step 1 of the two-phase payment flow. The intent reserves
1211
- * the payment amount and provides a client_secret for confirmation.
1212
- *
1213
- * @param request - Payment intent configuration
1214
- * @returns The created payment intent with client_secret
1215
- *
1216
- * @example
1217
- * ```typescript
1218
- * const intent = await zendfi.intents.create({
1219
- * amount: 49.99,
1220
- * description: 'Pro Plan - Monthly',
1221
- * capture_method: 'automatic', // or 'manual' for auth-only
1222
- * expires_in_seconds: 3600, // 1 hour
1223
- * });
1224
- *
1225
- * // Store intent.id and pass intent.client_secret to frontend
1226
- * console.log(`Intent created: ${intent.id}`);
1227
- * console.log(`Status: ${intent.status}`); // "requires_payment"
1228
- * ```
629
+ * Create an installment plan
630
+ * Split a purchase into multiple scheduled payments
1229
631
  */
1230
- async create(request) {
1231
- return this.request("POST", "/api/v1/payment-intents", {
1232
- amount: request.amount,
1233
- currency: request.currency || "USD",
1234
- description: request.description,
1235
- capture_method: request.capture_method || "automatic",
1236
- agent_id: request.agent_id,
1237
- agent_name: request.agent_name,
1238
- metadata: request.metadata,
1239
- expires_in_seconds: request.expires_in_seconds || 86400
1240
- // 24h default
1241
- });
632
+ async createInstallmentPlan(request) {
633
+ const response = await this.request(
634
+ "POST",
635
+ "/api/v1/installment-plans",
636
+ request
637
+ );
638
+ return {
639
+ id: response.plan_id,
640
+ plan_id: response.plan_id,
641
+ status: response.status
642
+ };
1242
643
  }
1243
644
  /**
1244
- * Get a payment intent by ID
1245
- *
1246
- * @param intentId - UUID of the payment intent
1247
- * @returns The payment intent details
1248
- *
1249
- * @example
1250
- * ```typescript
1251
- * const intent = await zendfi.intents.get('pi_123...');
1252
- * console.log(`Status: ${intent.status}`);
1253
- * if (intent.payment_id) {
1254
- * console.log(`Payment: ${intent.payment_id}`);
1255
- * }
1256
- * ```
645
+ * Get installment plan by ID
1257
646
  */
1258
- async get(intentId) {
1259
- return this.request("GET", `/api/v1/payment-intents/${intentId}`);
1260
- }
1261
- /**
1262
- * List payment intents
1263
- *
1264
- * @param options - Filter and pagination options
1265
- * @returns Array of payment intents
1266
- *
1267
- * @example
1268
- * ```typescript
1269
- * // Get recent pending intents
1270
- * const intents = await zendfi.intents.list({
1271
- * status: 'requires_payment',
1272
- * limit: 20,
1273
- * });
1274
- * ```
1275
- */
1276
- async list(options) {
1277
- const params = new URLSearchParams();
1278
- if (options?.status) params.append("status", options.status);
1279
- if (options?.limit) params.append("limit", options.limit.toString());
1280
- if (options?.offset) params.append("offset", options.offset.toString());
1281
- const query = params.toString() ? `?${params.toString()}` : "";
1282
- const response = await this.request(
1283
- "GET",
1284
- `/api/v1/payment-intents${query}`
1285
- );
1286
- return Array.isArray(response) ? response : response.intents;
1287
- }
1288
- /**
1289
- * Confirm a payment intent
1290
- *
1291
- * This is step 2 of the two-phase payment flow. Confirmation triggers
1292
- * the actual payment using the customer's wallet.
1293
- *
1294
- * @param intentId - UUID of the payment intent
1295
- * @param request - Confirmation details including customer wallet
1296
- * @returns The confirmed payment intent with payment_id
1297
- *
1298
- * @example
1299
- * ```typescript
1300
- * const confirmed = await zendfi.intents.confirm('pi_123...', {
1301
- * client_secret: 'pi_secret_abc...',
1302
- * customer_wallet: 'Hx7B...abc',
1303
- * auto_gasless: true,
1304
- * });
1305
- *
1306
- * if (confirmed.status === 'succeeded') {
1307
- * console.log(`Payment complete: ${confirmed.payment_id}`);
1308
- * }
1309
- * ```
1310
- */
1311
- async confirm(intentId, request) {
1312
- return this.request("POST", `/api/v1/payment-intents/${intentId}/confirm`, {
1313
- client_secret: request.client_secret,
1314
- customer_wallet: request.customer_wallet,
1315
- payment_type: request.payment_type,
1316
- auto_gasless: request.auto_gasless,
1317
- metadata: request.metadata,
1318
- session_token: request.session_token
1319
- });
1320
- }
1321
- /**
1322
- * Cancel a payment intent
1323
- *
1324
- * Canceling releases any hold on the payment amount. Cannot cancel
1325
- * intents that are already processing or succeeded.
1326
- *
1327
- * @param intentId - UUID of the payment intent
1328
- * @returns The canceled payment intent
1329
- *
1330
- * @example
1331
- * ```typescript
1332
- * const canceled = await zendfi.intents.cancel('pi_123...');
1333
- * console.log(`Status: ${canceled.status}`); // "canceled"
1334
- * ```
1335
- */
1336
- async cancel(intentId) {
1337
- return this.request("POST", `/api/v1/payment-intents/${intentId}/cancel`);
1338
- }
1339
- /**
1340
- * Get events for a payment intent
1341
- *
1342
- * Events track the full lifecycle of the intent, including creation,
1343
- * confirmation attempts, and status changes.
1344
- *
1345
- * @param intentId - UUID of the payment intent
1346
- * @returns Array of events in chronological order
1347
- *
1348
- * @example
1349
- * ```typescript
1350
- * const events = await zendfi.intents.getEvents('pi_123...');
1351
- * events.forEach(event => {
1352
- * console.log(`${event.created_at}: ${event.event_type}`);
1353
- * });
1354
- * ```
1355
- */
1356
- async getEvents(intentId) {
1357
- const response = await this.request(
1358
- "GET",
1359
- `/api/v1/payment-intents/${intentId}/events`
1360
- );
1361
- return Array.isArray(response) ? response : response.events;
1362
- }
1363
- };
1364
-
1365
- // src/aip/pricing.ts
1366
- var PricingAPI = class {
1367
- constructor(request) {
1368
- this.request = request;
1369
- }
1370
- /**
1371
- * Get PPP factor for a specific country
1372
- *
1373
- * Returns the purchasing power parity adjustment factor for the given
1374
- * country code. Use this to calculate localized pricing.
1375
- *
1376
- * @param countryCode - ISO 3166-1 alpha-2 country code (e.g., "BR", "IN", "NG")
1377
- * @returns PPP factor and suggested adjustment
1378
- *
1379
- * @example
1380
- * ```typescript
1381
- * const factor = await zendfi.pricing.getPPPFactor('BR');
1382
- * // {
1383
- * // country_code: 'BR',
1384
- * // country_name: 'Brazil',
1385
- * // ppp_factor: 0.35,
1386
- * // currency_code: 'BRL',
1387
- * // adjustment_percentage: 35.0
1388
- * // }
1389
- *
1390
- * // Calculate localized price
1391
- * const usdPrice = 100;
1392
- * const localPrice = usdPrice * (1 - factor.adjustment_percentage / 100);
1393
- * console.log(`$${localPrice} for Brazilian customers`);
1394
- * ```
1395
- */
1396
- async getPPPFactor(countryCode) {
1397
- return this.request("POST", "/api/v1/ai/pricing/ppp-factor", {
1398
- country_code: countryCode.toUpperCase()
1399
- });
1400
- }
1401
- /**
1402
- * List all available PPP factors
1403
- *
1404
- * Returns PPP factors for all supported countries. Useful for building
1405
- * pricing tables or pre-computing regional prices.
1406
- *
1407
- * @returns Array of PPP factors for all supported countries
1408
- *
1409
- * @example
1410
- * ```typescript
1411
- * const factors = await zendfi.pricing.listFactors();
1412
- *
1413
- * // Create pricing tiers
1414
- * const tiers = factors.map(f => ({
1415
- * country: f.country_name,
1416
- * price: (100 * f.ppp_factor).toFixed(2),
1417
- * }));
1418
- *
1419
- * console.table(tiers);
1420
- * ```
1421
- */
1422
- async listFactors() {
1423
- const response = await this.request(
1424
- "GET",
1425
- "/api/v1/ai/pricing/ppp-factors"
1426
- );
1427
- return Array.isArray(response) ? response : response.factors;
1428
- }
1429
- /**
1430
- * Get AI-powered pricing suggestion
1431
- *
1432
- * Returns an intelligent pricing recommendation based on the user's
1433
- * location, wallet history, and your pricing configuration.
1434
- *
1435
- * @param request - Pricing suggestion request with user context
1436
- * @returns AI-generated pricing suggestion with reasoning
1437
- *
1438
- * @example
1439
- * ```typescript
1440
- * const suggestion = await zendfi.pricing.getSuggestion({
1441
- * agent_id: 'shopping-assistant',
1442
- * base_price: 99.99,
1443
- * user_profile: {
1444
- * location_country: 'BR',
1445
- * context: 'first-time',
1446
- * },
1447
- * ppp_config: {
1448
- * enabled: true,
1449
- * max_discount_percent: 50,
1450
- * floor_price: 29.99,
1451
- * },
1452
- * });
1453
- *
1454
- * console.log(`Suggested: $${suggestion.suggested_amount}`);
1455
- * console.log(`Reason: ${suggestion.reasoning}`);
1456
- * // => "Price adjusted for Brazilian purchasing power (35% PPP discount)
1457
- * // plus 10% first-time customer discount"
1458
- * ```
1459
- */
1460
- async getSuggestion(request) {
1461
- return this.request("POST", "/api/v1/ai/pricing/suggest", {
1462
- agent_id: request.agent_id,
1463
- product_id: request.product_id,
1464
- base_price: request.base_price,
1465
- currency: request.currency || "USD",
1466
- user_profile: request.user_profile,
1467
- ppp_config: request.ppp_config
1468
- });
1469
- }
1470
- /**
1471
- * Calculate localized price for a given base price and country
1472
- *
1473
- * Convenience method that combines getPPPFactor with price calculation.
1474
- *
1475
- * @param basePrice - Original price in USD
1476
- * @param countryCode - ISO 3166-1 alpha-2 country code
1477
- * @returns Object with original and adjusted prices
1478
- *
1479
- * @example
1480
- * ```typescript
1481
- * const result = await zendfi.pricing.calculateLocalPrice(100, 'IN');
1482
- * console.log(`Original: $${result.original}`);
1483
- * console.log(`Local: $${result.adjusted}`);
1484
- * console.log(`Savings: $${result.savings} (${result.discount_percentage}%)`);
1485
- * ```
1486
- */
1487
- async calculateLocalPrice(basePrice, countryCode) {
1488
- const factor = await this.getPPPFactor(countryCode);
1489
- const adjusted = Number((basePrice * factor.ppp_factor).toFixed(2));
1490
- const savings = Number((basePrice - adjusted).toFixed(2));
1491
- return {
1492
- original: basePrice,
1493
- adjusted,
1494
- savings,
1495
- discount_percentage: factor.adjustment_percentage,
1496
- country: factor.country_name,
1497
- ppp_factor: factor.ppp_factor
1498
- };
1499
- }
1500
- };
1501
-
1502
- // src/aip/autonomy.ts
1503
- var AutonomyAPI = class {
1504
- constructor(request) {
1505
- this.request = request;
1506
- }
1507
- /**
1508
- * Enable autonomous signing for a session key
1509
- *
1510
- * This grants an AI agent the ability to sign transactions on behalf of
1511
- * the user, up to the specified spending limit and duration.
1512
- *
1513
- * **Prerequisites:**
1514
- * 1. Create a device-bound session key first
1515
- * 2. Generate a delegation signature (see `createDelegationMessage`)
1516
- * 3. Optionally encrypt keypair with Lit Protocol for true autonomy
1517
- *
1518
- * @param sessionKeyId - UUID of the session key
1519
- * @param request - Autonomy configuration including delegation signature
1520
- * @returns The created autonomous delegate
1521
- *
1522
- * @example
1523
- * ```typescript
1524
- * // The user must sign this exact message format
1525
- * const message = zendfi.autonomy.createDelegationMessage(
1526
- * sessionKeyId, 100, '2024-12-10T00:00:00Z'
1527
- * );
1528
- *
1529
- * // Have user sign with their session key
1530
- * const signature = await signWithSessionKey(message, pin);
1531
- *
1532
- * // Enable autonomous mode
1533
- * const delegate = await zendfi.autonomy.enable(sessionKeyId, {
1534
- * max_amount_usd: 100,
1535
- * duration_hours: 24,
1536
- * delegation_signature: signature,
1537
- * });
1538
- *
1539
- * console.log(`Delegate ID: ${delegate.delegate_id}`);
1540
- * console.log(`Expires: ${delegate.expires_at}`);
1541
- * ```
1542
- */
1543
- async enable(sessionKeyId, request) {
1544
- return this.request(
1545
- "POST",
1546
- `/api/v1/ai/session-keys/${sessionKeyId}/enable-autonomy`,
1547
- {
1548
- max_amount_usd: request.max_amount_usd,
1549
- duration_hours: request.duration_hours,
1550
- delegation_signature: request.delegation_signature,
1551
- expires_at: request.expires_at,
1552
- lit_encrypted_keypair: request.lit_encrypted_keypair,
1553
- lit_data_hash: request.lit_data_hash,
1554
- metadata: request.metadata
1555
- }
1556
- );
1557
- }
1558
- /**
1559
- * Revoke autonomous mode for a session key
1560
- *
1561
- * Immediately invalidates the autonomous delegate, preventing any further
1562
- * automatic payments. The session key itself remains valid for manual use.
1563
- *
1564
- * @param sessionKeyId - UUID of the session key
1565
- * @param reason - Optional reason for revocation (logged for audit)
1566
- *
1567
- * @example
1568
- * ```typescript
1569
- * await zendfi.autonomy.revoke('sk_123...', 'User requested revocation');
1570
- * console.log('Autonomous mode disabled');
1571
- * ```
1572
- */
1573
- async revoke(sessionKeyId, reason) {
1574
- const request = { reason };
1575
- await this.request(
1576
- "POST",
1577
- `/api/v1/ai/session-keys/${sessionKeyId}/revoke-autonomy`,
1578
- request
1579
- );
1580
- }
1581
- /**
1582
- * Get autonomy status for a session key
1583
- *
1584
- * Returns whether autonomous mode is enabled and details about the
1585
- * active delegate including remaining spending allowance.
1586
- *
1587
- * @param sessionKeyId - UUID of the session key
1588
- * @returns Autonomy status with delegate details
1589
- *
1590
- * @example
1591
- * ```typescript
1592
- * const status = await zendfi.autonomy.getStatus('sk_123...');
1593
- *
1594
- * if (status.autonomous_mode_enabled && status.delegate) {
1595
- * console.log(`Remaining: $${status.delegate.remaining_usd}`);
1596
- * console.log(`Expires: ${status.delegate.expires_at}`);
1597
- * } else {
1598
- * console.log('Autonomous mode not enabled');
1599
- * }
1600
- * ```
1601
- */
1602
- async getStatus(sessionKeyId) {
1603
- return this.request(
1604
- "GET",
1605
- `/api/v1/ai/session-keys/${sessionKeyId}/autonomy-status`
1606
- );
1607
- }
1608
- /**
1609
- * Create the delegation message that needs to be signed
1610
- *
1611
- * This generates the exact message format required for the delegation
1612
- * signature. The user must sign this message with their session key.
1613
- *
1614
- * **Message format:**
1615
- * ```
1616
- * I authorize autonomous delegate for session {id} to spend up to ${amount} until {expiry}
1617
- * ```
1618
- *
1619
- * @param sessionKeyId - UUID of the session key
1620
- * @param maxAmountUsd - Maximum spending amount in USD
1621
- * @param expiresAt - ISO 8601 expiration timestamp
1622
- * @returns The message to be signed
1623
- *
1624
- * @example
1625
- * ```typescript
1626
- * const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
1627
- * const message = zendfi.autonomy.createDelegationMessage(
1628
- * 'sk_123...',
1629
- * 100,
1630
- * expiresAt
1631
- * );
1632
- * // => "I authorize autonomous delegate for session sk_123... to spend up to $100 until 2024-12-06T..."
1633
- *
1634
- * // Sign with nacl.sign.detached() or similar
1635
- * const signature = signMessage(message, keypair);
1636
- * ```
1637
- */
1638
- createDelegationMessage(sessionKeyId, maxAmountUsd, expiresAt) {
1639
- return `I authorize autonomous delegate for session ${sessionKeyId} to spend up to $${maxAmountUsd} until ${expiresAt}`;
1640
- }
1641
- /**
1642
- * Validate delegation signature parameters
1643
- *
1644
- * Helper method to check if autonomy parameters are valid before
1645
- * making the API call.
1646
- *
1647
- * @param request - The enable autonomy request to validate
1648
- * @throws Error if validation fails
1649
- *
1650
- * @example
1651
- * ```typescript
1652
- * try {
1653
- * zendfi.autonomy.validateRequest(request);
1654
- * const delegate = await zendfi.autonomy.enable(sessionKeyId, request);
1655
- * } catch (error) {
1656
- * console.error('Invalid request:', error.message);
1657
- * }
1658
- * ```
1659
- */
1660
- validateRequest(request) {
1661
- if (request.max_amount_usd <= 0) {
1662
- throw new Error("max_amount_usd must be positive");
1663
- }
1664
- if (request.duration_hours < 1 || request.duration_hours > 168) {
1665
- throw new Error("duration_hours must be between 1 and 168 (7 days)");
1666
- }
1667
- if (!request.delegation_signature || request.delegation_signature.length === 0) {
1668
- throw new Error("delegation_signature is required");
1669
- }
1670
- const base64Regex = /^[A-Za-z0-9+/]+=*$/;
1671
- if (!base64Regex.test(request.delegation_signature)) {
1672
- throw new Error("delegation_signature must be base64 encoded");
1673
- }
1674
- }
1675
- /**
1676
- * Get spending attestations for a delegate (audit trail)
1677
- *
1678
- * Returns all cryptographically signed attestations ZendFi created for
1679
- * this delegate. Each attestation contains:
1680
- * - The spending state at the time of payment
1681
- * - ZendFi's Ed25519 signature
1682
- * - Timestamp and nonce (for replay protection)
1683
- *
1684
- * These attestations can be independently verified using ZendFi's public key
1685
- * to confirm spending limit enforcement was applied correctly.
1686
- *
1687
- * @param delegateId - UUID of the autonomous delegate
1688
- * @returns Attestation audit response with all signed attestations
1689
- *
1690
- * @example
1691
- * ```typescript
1692
- * const audit = await zendfi.autonomy.getAttestations('delegate_123...');
1693
- *
1694
- * console.log(`Found ${audit.attestation_count} attestations`);
1695
- * console.log(`ZendFi public key: ${audit.zendfi_attestation_public_key}`);
1696
- *
1697
- * // Verify each attestation independently
1698
- * for (const signed of audit.attestations) {
1699
- * console.log(`Payment ${signed.attestation.payment_id}:`);
1700
- * console.log(` Requested: $${signed.attestation.requested_usd}`);
1701
- * console.log(` Remaining after: $${signed.attestation.remaining_after_usd}`);
1702
- * // Verify signature with nacl.sign.detached.verify()
1703
- * }
1704
- * ```
1705
- */
1706
- async getAttestations(delegateId) {
1707
- return this.request(
1708
- "GET",
1709
- `/api/v1/ai/delegates/${delegateId}/attestations`
1710
- );
1711
- }
1712
- };
1713
-
1714
- // src/aip/smart-payments.ts
1715
- var SmartPaymentsAPI = class {
1716
- constructor(request) {
1717
- this.request = request;
1718
- }
1719
- /**
1720
- * Execute an AI-powered smart payment
1721
- *
1722
- * Smart payments analyze the context and automatically apply optimizations:
1723
- * - **PPP Pricing**: Auto-adjusts based on customer location
1724
- * - **Gasless**: Detects when user needs gas subsidization
1725
- * - **Instant Settlement**: Optional immediate merchant payout
1726
- * - **Escrow**: Optional fund holding for service delivery
1727
- *
1728
- * @param request - Smart payment request configuration
1729
- * @returns Payment result with status and receipt
1730
- *
1731
- * @example
1732
- * ```typescript
1733
- * // Basic smart payment
1734
- * const result = await zendfi.payments.smart({
1735
- * agent_id: 'my-agent',
1736
- * user_wallet: 'Hx7B...abc',
1737
- * amount_usd: 99.99,
1738
- * description: 'Annual Pro Plan',
1739
- * });
1740
- *
1741
- * // With all options
1742
- * const result = await zendfi.payments.smart({
1743
- * agent_id: 'my-agent',
1744
- * session_token: 'zai_session_...', // For limit enforcement
1745
- * user_wallet: 'Hx7B...abc',
1746
- * amount_usd: 99.99,
1747
- * token: 'USDC',
1748
- * auto_detect_gasless: true,
1749
- * instant_settlement: true,
1750
- * enable_escrow: false,
1751
- * description: 'Annual Pro Plan',
1752
- * product_details: {
1753
- * name: 'Pro Plan',
1754
- * sku: 'PRO-ANNUAL',
1755
- * },
1756
- * metadata: {
1757
- * user_id: 'usr_123',
1758
- * },
1759
- * });
1760
- *
1761
- * if (result.requires_signature) {
1762
- * // Device-bound flow: need user to sign
1763
- * console.log('Please sign:', result.unsigned_transaction);
1764
- * console.log('Submit to:', result.submit_url);
1765
- * } else {
1766
- * // Auto-signed (custodial or autonomous delegate)
1767
- * console.log('Payment complete:', result.transaction_signature);
1768
- * }
1769
- * ```
1770
- */
1771
- async execute(request) {
1772
- return this.request("POST", "/api/v1/ai/smart-payment", {
1773
- session_token: request.session_token,
1774
- agent_id: request.agent_id,
1775
- user_wallet: request.user_wallet,
1776
- amount_usd: request.amount_usd,
1777
- merchant_id: request.merchant_id,
1778
- token: request.token || "USDC",
1779
- auto_detect_gasless: request.auto_detect_gasless,
1780
- instant_settlement: request.instant_settlement,
1781
- enable_escrow: request.enable_escrow,
1782
- description: request.description,
1783
- product_details: request.product_details,
1784
- metadata: request.metadata
1785
- });
1786
- }
1787
- /**
1788
- * Submit a signed transaction from device-bound flow
1789
- *
1790
- * When a smart payment returns `requires_signature: true`, the client
1791
- * must sign the transaction and submit it here.
1792
- *
1793
- * @param paymentId - UUID of the payment
1794
- * @param signedTransaction - Base64 encoded signed transaction
1795
- * @returns Updated payment response
1796
- *
1797
- * @example
1798
- * ```typescript
1799
- * // After user signs the transaction
1800
- * const result = await zendfi.payments.submitSigned(
1801
- * payment.payment_id,
1802
- * signedTransaction
1803
- * );
1804
- *
1805
- * console.log(`Confirmed in ${result.confirmed_in_ms}ms`);
1806
- * ```
1807
- */
1808
- async submitSigned(paymentId, signedTransaction) {
1809
- return this.request(
1810
- "POST",
1811
- `/api/v1/ai/payments/${paymentId}/submit-signed`,
1812
- {
1813
- signed_transaction: signedTransaction
1814
- }
1815
- );
1816
- }
1817
- };
1818
-
1819
- // src/device-bound-crypto.ts
1820
- var import_web3 = require("@solana/web3.js");
1821
- var crypto2 = __toESM(require("crypto"));
1822
- var DeviceFingerprintGenerator = class {
1823
- /**
1824
- * Generate a unique device fingerprint
1825
- * Combines multiple browser attributes for uniqueness
1826
- */
1827
- static async generate() {
1828
- const components = {};
1829
- try {
1830
- components.canvas = await this.getCanvasFingerprint();
1831
- components.webgl = await this.getWebGLFingerprint();
1832
- components.audio = await this.getAudioFingerprint();
1833
- if (typeof screen !== "undefined") {
1834
- components.screen = `${screen.width}x${screen.height}x${screen.colorDepth}`;
1835
- } else {
1836
- components.screen = "unknown-ssr";
1837
- }
1838
- components.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
1839
- if (typeof navigator !== "undefined") {
1840
- components.languages = navigator.languages?.join(",") || navigator.language;
1841
- components.platform = navigator.platform;
1842
- components.hardwareConcurrency = navigator.hardwareConcurrency?.toString() || "unknown";
1843
- } else {
1844
- components.languages = "unknown-ssr";
1845
- components.platform = "unknown-ssr";
1846
- components.hardwareConcurrency = "unknown-ssr";
1847
- }
1848
- const combined = Object.entries(components).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}:${value}`).join("|");
1849
- const fingerprint = await this.sha256(combined);
1850
- return {
1851
- fingerprint,
1852
- generatedAt: Date.now(),
1853
- components
1854
- };
1855
- } catch (error) {
1856
- console.warn("Device fingerprinting failed, using fallback", error);
1857
- return this.generateFallbackFingerprint();
1858
- }
1859
- }
1860
- /**
1861
- * Graceful fallback fingerprint generation
1862
- * Works in headless browsers, SSR, and restricted environments
1863
- */
1864
- static async generateFallbackFingerprint() {
1865
- const components = {};
1866
- try {
1867
- if (typeof navigator !== "undefined") {
1868
- components.platform = navigator.platform || "unknown";
1869
- components.languages = navigator.languages?.join(",") || navigator.language || "unknown";
1870
- components.hardwareConcurrency = navigator.hardwareConcurrency?.toString() || "unknown";
1871
- }
1872
- if (typeof screen !== "undefined") {
1873
- components.screen = `${screen.width || 0}x${screen.height || 0}x${screen.colorDepth || 0}`;
1874
- }
1875
- if (typeof Intl !== "undefined") {
1876
- try {
1877
- components.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
1878
- } catch {
1879
- components.timezone = "unknown";
1880
- }
1881
- }
1882
- } catch {
1883
- components.platform = "fallback";
1884
- }
1885
- let randomEntropy = "";
1886
- try {
1887
- if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
1888
- const arr = new Uint8Array(16);
1889
- window.crypto.getRandomValues(arr);
1890
- randomEntropy = Array.from(arr).map((b) => b.toString(16).padStart(2, "0")).join("");
1891
- } else if (typeof crypto2 !== "undefined" && crypto2.randomBytes) {
1892
- randomEntropy = crypto2.randomBytes(16).toString("hex");
1893
- }
1894
- } catch {
1895
- randomEntropy = Date.now().toString(36) + Math.random().toString(36);
1896
- }
1897
- const combined = Object.entries(components).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}:${value}`).join("|") + "|entropy:" + randomEntropy;
1898
- const fingerprint = await this.sha256(combined);
1899
- return {
1900
- fingerprint,
1901
- generatedAt: Date.now(),
1902
- components
1903
- };
1904
- }
1905
- static async getCanvasFingerprint() {
1906
- if (typeof document === "undefined") return "no-canvas-ssr";
1907
- const canvas = document.createElement("canvas");
1908
- const ctx = canvas.getContext("2d");
1909
- if (!ctx) return "no-canvas";
1910
- canvas.width = 200;
1911
- canvas.height = 50;
1912
- ctx.textBaseline = "top";
1913
- ctx.font = '14px "Arial"';
1914
- ctx.fillStyle = "#f60";
1915
- ctx.fillRect(0, 0, 100, 50);
1916
- ctx.fillStyle = "#069";
1917
- ctx.fillText("ZendFi \u{1F510}", 2, 2);
1918
- ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
1919
- ctx.fillText("Device-Bound", 4, 17);
1920
- return canvas.toDataURL();
1921
- }
1922
- static async getWebGLFingerprint() {
1923
- if (typeof document === "undefined") return "no-webgl-ssr";
1924
- const canvas = document.createElement("canvas");
1925
- const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
1926
- if (!gl) return "no-webgl";
1927
- const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
1928
- if (!debugInfo) return "no-debug-info";
1929
- const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
1930
- const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
1931
- return `${vendor}|${renderer}`;
1932
- }
1933
- static async getAudioFingerprint() {
1934
- try {
1935
- if (typeof window === "undefined") return "no-audio-ssr";
1936
- const AudioContext = window.AudioContext || window.webkitAudioContext;
1937
- if (!AudioContext) return "no-audio";
1938
- const context = new AudioContext();
1939
- const oscillator = context.createOscillator();
1940
- const analyser = context.createAnalyser();
1941
- const gainNode = context.createGain();
1942
- const scriptProcessor = context.createScriptProcessor(4096, 1, 1);
1943
- gainNode.gain.value = 0;
1944
- oscillator.connect(analyser);
1945
- analyser.connect(scriptProcessor);
1946
- scriptProcessor.connect(gainNode);
1947
- gainNode.connect(context.destination);
1948
- oscillator.start(0);
1949
- return new Promise((resolve) => {
1950
- scriptProcessor.onaudioprocess = (event) => {
1951
- const output = event.inputBuffer.getChannelData(0);
1952
- const hash = Array.from(output.slice(0, 30)).reduce((acc, val) => acc + Math.abs(val), 0);
1953
- oscillator.stop();
1954
- scriptProcessor.disconnect();
1955
- context.close();
1956
- resolve(hash.toString());
1957
- };
1958
- });
1959
- } catch (error) {
1960
- return "audio-error";
1961
- }
1962
- }
1963
- static async sha256(data) {
1964
- if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
1965
- const encoder = new TextEncoder();
1966
- const dataBuffer = encoder.encode(data);
1967
- const hashBuffer = await window.crypto.subtle.digest("SHA-256", dataBuffer);
1968
- const hashArray = Array.from(new Uint8Array(hashBuffer));
1969
- return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
1970
- } else {
1971
- return crypto2.createHash("sha256").update(data).digest("hex");
1972
- }
1973
- }
1974
- };
1975
- var SessionKeyCrypto = class {
1976
- /**
1977
- * Encrypt a Solana keypair with PIN + device fingerprint
1978
- * Uses Argon2id for key derivation and AES-256-GCM for encryption
1979
- */
1980
- static async encrypt(keypair, pin, deviceFingerprint) {
1981
- if (!/^\d{6}$/.test(pin)) {
1982
- throw new Error("PIN must be exactly 6 numeric digits");
1983
- }
1984
- const encryptionKey = await this.deriveKey(pin, deviceFingerprint);
1985
- const nonce = this.generateNonce();
1986
- const secretKey = keypair.secretKey;
1987
- const encryptedData = await this.aesEncrypt(secretKey, encryptionKey, nonce);
1988
- return {
1989
- encryptedData: Buffer.from(encryptedData).toString("base64"),
1990
- nonce: Buffer.from(nonce).toString("base64"),
1991
- publicKey: keypair.publicKey.toBase58(),
1992
- deviceFingerprint,
1993
- version: "argon2id-aes256gcm-v1"
1994
- };
1995
- }
1996
- /**
1997
- * Decrypt an encrypted session key with PIN + device fingerprint
1998
- */
1999
- static async decrypt(encrypted, pin, deviceFingerprint) {
2000
- if (!/^\d{6}$/.test(pin)) {
2001
- throw new Error("PIN must be exactly 6 numeric digits");
2002
- }
2003
- if (encrypted.deviceFingerprint !== deviceFingerprint) {
2004
- throw new Error("Device fingerprint mismatch - wrong device or security threat");
2005
- }
2006
- const encryptionKey = await this.deriveKey(pin, deviceFingerprint);
2007
- const encryptedData = Buffer.from(encrypted.encryptedData, "base64");
2008
- const nonce = Buffer.from(encrypted.nonce, "base64");
2009
- try {
2010
- const secretKey = await this.aesDecrypt(encryptedData, encryptionKey, nonce);
2011
- return import_web3.Keypair.fromSecretKey(secretKey);
2012
- } catch (error) {
2013
- throw new Error("Decryption failed - wrong PIN or corrupted data");
2014
- }
2015
- }
2016
- /**
2017
- * Derive encryption key from PIN + device fingerprint using Argon2id
2018
- *
2019
- * Argon2id parameters (OWASP recommended):
2020
- * - Memory: 64MB (65536 KB)
2021
- * - Iterations: 3
2022
- * - Parallelism: 4
2023
- * - Salt: device fingerprint
2024
- */
2025
- static async deriveKey(pin, deviceFingerprint) {
2026
- if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
2027
- const encoder = new TextEncoder();
2028
- const keyMaterial = await window.crypto.subtle.importKey(
2029
- "raw",
2030
- encoder.encode(pin),
2031
- { name: "PBKDF2" },
2032
- false,
2033
- ["deriveBits"]
2034
- );
2035
- const derivedBits = await window.crypto.subtle.deriveBits(
2036
- {
2037
- name: "PBKDF2",
2038
- salt: encoder.encode(deviceFingerprint),
2039
- iterations: 1e5,
2040
- // High iteration count for security
2041
- hash: "SHA-256"
2042
- },
2043
- keyMaterial,
2044
- 256
2045
- // 256 bits = 32 bytes for AES-256
2046
- );
2047
- return new Uint8Array(derivedBits);
2048
- } else {
2049
- const salt = crypto2.createHash("sha256").update(deviceFingerprint).digest();
2050
- return crypto2.pbkdf2Sync(pin, salt, 1e5, 32, "sha256");
2051
- }
2052
- }
2053
- /**
2054
- * Generate random nonce for AES-GCM (12 bytes)
2055
- */
2056
- static generateNonce() {
2057
- if (typeof window !== "undefined" && window.crypto) {
2058
- return window.crypto.getRandomValues(new Uint8Array(12));
2059
- } else {
2060
- return crypto2.randomBytes(12);
2061
- }
2062
- }
2063
- /**
2064
- * Encrypt with AES-256-GCM
2065
- */
2066
- static async aesEncrypt(plaintext, key, nonce) {
2067
- if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
2068
- const keyBuffer = key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength);
2069
- const nonceBuffer = nonce.buffer.slice(nonce.byteOffset, nonce.byteOffset + nonce.byteLength);
2070
- const plaintextBuffer = plaintext.buffer.slice(plaintext.byteOffset, plaintext.byteOffset + plaintext.byteLength);
2071
- const cryptoKey = await window.crypto.subtle.importKey(
2072
- "raw",
2073
- keyBuffer,
2074
- { name: "AES-GCM" },
2075
- false,
2076
- ["encrypt"]
2077
- );
2078
- const encrypted = await window.crypto.subtle.encrypt(
2079
- {
2080
- name: "AES-GCM",
2081
- iv: nonceBuffer
2082
- },
2083
- cryptoKey,
2084
- plaintextBuffer
2085
- );
2086
- return new Uint8Array(encrypted);
2087
- } else {
2088
- const cipher = crypto2.createCipheriv("aes-256-gcm", key, nonce);
2089
- const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
2090
- const authTag = cipher.getAuthTag();
2091
- return new Uint8Array(Buffer.concat([encrypted, authTag]));
2092
- }
2093
- }
2094
- /**
2095
- * Decrypt with AES-256-GCM
2096
- */
2097
- static async aesDecrypt(ciphertext, key, nonce) {
2098
- if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
2099
- const keyBuffer = key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength);
2100
- const nonceBuffer = nonce.buffer.slice(nonce.byteOffset, nonce.byteOffset + nonce.byteLength);
2101
- const ciphertextBuffer = ciphertext.buffer.slice(ciphertext.byteOffset, ciphertext.byteOffset + ciphertext.byteLength);
2102
- const cryptoKey = await window.crypto.subtle.importKey(
2103
- "raw",
2104
- keyBuffer,
2105
- { name: "AES-GCM" },
2106
- false,
2107
- ["decrypt"]
2108
- );
2109
- const decrypted = await window.crypto.subtle.decrypt(
2110
- {
2111
- name: "AES-GCM",
2112
- iv: nonceBuffer
2113
- },
2114
- cryptoKey,
2115
- ciphertextBuffer
2116
- );
2117
- return new Uint8Array(decrypted);
2118
- } else {
2119
- const authTag = ciphertext.slice(-16);
2120
- const encrypted = ciphertext.slice(0, -16);
2121
- const decipher = crypto2.createDecipheriv("aes-256-gcm", key, nonce);
2122
- decipher.setAuthTag(authTag);
2123
- const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
2124
- return new Uint8Array(decrypted);
2125
- }
2126
- }
2127
- };
2128
- var RecoveryQRGenerator = class {
2129
- /**
2130
- * Generate recovery QR data
2131
- * This allows users to recover their session key on a new device
2132
- */
2133
- static generate(encrypted) {
2134
- return {
2135
- encryptedSessionKey: encrypted.encryptedData,
2136
- nonce: encrypted.nonce,
2137
- publicKey: encrypted.publicKey,
2138
- version: "v1",
2139
- createdAt: Date.now()
2140
- };
2141
- }
2142
- /**
2143
- * Encode recovery QR as JSON string
2144
- */
2145
- static encode(recoveryQR) {
2146
- return JSON.stringify(recoveryQR);
2147
- }
2148
- /**
2149
- * Decode recovery QR from JSON string
2150
- */
2151
- static decode(qrData) {
2152
- try {
2153
- const parsed = JSON.parse(qrData);
2154
- if (!parsed.encryptedSessionKey || !parsed.nonce || !parsed.publicKey) {
2155
- throw new Error("Invalid recovery QR data");
2156
- }
2157
- return parsed;
2158
- } catch (error) {
2159
- throw new Error("Failed to decode recovery QR");
2160
- }
2161
- }
2162
- /**
2163
- * Re-encrypt session key for new device
2164
- */
2165
- static async reEncryptForNewDevice(recoveryQR, oldPin, oldDeviceFingerprint, newPin, newDeviceFingerprint) {
2166
- const oldEncrypted = {
2167
- encryptedData: recoveryQR.encryptedSessionKey,
2168
- nonce: recoveryQR.nonce,
2169
- publicKey: recoveryQR.publicKey,
2170
- deviceFingerprint: oldDeviceFingerprint,
2171
- version: "argon2id-aes256gcm-v1"
2172
- };
2173
- const keypair = await SessionKeyCrypto.decrypt(oldEncrypted, oldPin, oldDeviceFingerprint);
2174
- return await SessionKeyCrypto.encrypt(keypair, newPin, newDeviceFingerprint);
2175
- }
2176
- };
2177
- var DeviceBoundSessionKey = class _DeviceBoundSessionKey {
2178
- encrypted = null;
2179
- deviceFingerprint = null;
2180
- sessionKeyId = null;
2181
- recoveryQR = null;
2182
- originalKeypair = null;
2183
- // Store for Lit encryption
2184
- // Auto-signing cache: decrypted keypair stored in memory
2185
- // Enables instant signing without re-entering PIN for subsequent payments
2186
- cachedKeypair = null;
2187
- cacheExpiry = null;
2188
- // Timestamp when cache expires
2189
- DEFAULT_CACHE_TTL_MS = 30 * 60 * 1e3;
2190
- // 30 minutes
2191
- /**
2192
- * Create a new device-bound session key
2193
- */
2194
- static async create(options) {
2195
- const deviceFingerprint = await DeviceFingerprintGenerator.generate();
2196
- const keypair = import_web3.Keypair.generate();
2197
- const encrypted = await SessionKeyCrypto.encrypt(
2198
- keypair,
2199
- options.pin,
2200
- deviceFingerprint.fingerprint
2201
- );
2202
- const instance = new _DeviceBoundSessionKey();
2203
- instance.encrypted = encrypted;
2204
- instance.deviceFingerprint = deviceFingerprint;
2205
- instance.originalKeypair = keypair;
2206
- if (options.generateRecoveryQR) {
2207
- instance.recoveryQR = RecoveryQRGenerator.generate(encrypted);
2208
- }
2209
- return instance;
2210
- }
2211
- /**
2212
- * Get the original keypair (only available immediately after creation)
2213
- * Used for Lit Protocol encryption during session creation
2214
- * @internal
2215
- */
2216
- getKeypair() {
2217
- if (!this.originalKeypair) {
2218
- throw new Error("Keypair not available - only accessible during session creation");
2219
- }
2220
- return this.originalKeypair;
2221
- }
2222
- /**
2223
- * Get encrypted data for backend storage
2224
- */
2225
- getEncryptedData() {
2226
- if (!this.encrypted) {
2227
- throw new Error("Session key not created yet");
2228
- }
2229
- return this.encrypted;
2230
- }
2231
- /**
2232
- * Get device fingerprint
2233
- */
2234
- getDeviceFingerprint() {
2235
- if (!this.deviceFingerprint) {
2236
- throw new Error("Device fingerprint not generated yet");
2237
- }
2238
- return this.deviceFingerprint.fingerprint;
2239
- }
2240
- /**
2241
- * Get public key
2242
- */
2243
- getPublicKey() {
2244
- if (!this.encrypted) {
2245
- throw new Error("Session key not created yet");
2246
- }
2247
- return this.encrypted.publicKey;
2248
- }
2249
- /**
2250
- * Get recovery QR data (if generated)
2251
- */
2252
- getRecoveryQR() {
2253
- return this.recoveryQR;
2254
- }
2255
- /**
2256
- * Decrypt and sign a transaction
2257
- *
2258
- * @param transaction - The transaction to sign
2259
- * @param pin - User's PIN (required only if keypair not cached)
2260
- * @param cacheKeypair - Whether to cache the decrypted keypair for future use (default: true)
2261
- * @param cacheTTL - Cache time-to-live in milliseconds (default: 30 minutes)
2262
- *
2263
- * @example
2264
- * ```typescript
2265
- * // First payment: requires PIN, caches keypair
2266
- * await sessionKey.signTransaction(tx1, '123456', true);
2267
- *
2268
- * // Subsequent payments: uses cached keypair, no PIN needed!
2269
- * await sessionKey.signTransaction(tx2, '', false); // PIN ignored if cached
2270
- *
2271
- * // Clear cache when done
2272
- * sessionKey.clearCache();
2273
- * ```
2274
- */
2275
- async signTransaction(transaction, pin = "", cacheKeypair = true, cacheTTL) {
2276
- if (!this.encrypted || !this.deviceFingerprint) {
2277
- throw new Error("Session key not initialized");
2278
- }
2279
- let keypair;
2280
- if (this.isCached()) {
2281
- keypair = this.cachedKeypair;
2282
- } else {
2283
- if (!pin) {
2284
- throw new Error("PIN required: no cached keypair available");
2285
- }
2286
- keypair = await SessionKeyCrypto.decrypt(
2287
- this.encrypted,
2288
- pin,
2289
- this.deviceFingerprint.fingerprint
2290
- );
2291
- if (cacheKeypair) {
2292
- const ttl = cacheTTL || this.DEFAULT_CACHE_TTL_MS;
2293
- this.cacheKeypair(keypair, ttl);
2294
- }
2295
- }
2296
- transaction.partialSign(keypair);
2297
- return transaction;
2298
- }
2299
- /**
2300
- * Check if keypair is cached and valid
2301
- */
2302
- isCached() {
2303
- if (!this.cachedKeypair || !this.cacheExpiry) {
2304
- return false;
2305
- }
2306
- const now = Date.now();
2307
- if (now > this.cacheExpiry) {
2308
- this.clearCache();
2309
- return false;
2310
- }
2311
- return true;
2312
- }
2313
- /**
2314
- * Manually cache a keypair
2315
- * Called internally after PIN decryption
2316
- */
2317
- cacheKeypair(keypair, ttl) {
2318
- this.cachedKeypair = keypair;
2319
- this.cacheExpiry = Date.now() + ttl;
2320
- }
2321
- /**
2322
- * Clear cached keypair
2323
- * Should be called when user logs out or session ends
2324
- *
2325
- * @example
2326
- * ```typescript
2327
- * // Clear cache on logout
2328
- * sessionKey.clearCache();
2329
- *
2330
- * // Or clear automatically on tab close
2331
- * window.addEventListener('beforeunload', () => {
2332
- * sessionKey.clearCache();
2333
- * });
2334
- * ```
2335
- */
2336
- clearCache() {
2337
- this.cachedKeypair = null;
2338
- this.cacheExpiry = null;
2339
- }
2340
- /**
2341
- * Decrypt and cache keypair without signing a transaction
2342
- * Useful for pre-warming the cache before user makes payments
2343
- *
2344
- * @example
2345
- * ```typescript
2346
- * // After session key creation, decrypt and cache
2347
- * await sessionKey.unlockWithPin('123456');
2348
- *
2349
- * // Now all subsequent payments are instant (no PIN)
2350
- * await sessionKey.signTransaction(tx1, '', false); // Instant!
2351
- * await sessionKey.signTransaction(tx2, '', false); // Instant!
2352
- * ```
2353
- */
2354
- async unlockWithPin(pin, cacheTTL) {
2355
- if (!this.encrypted || !this.deviceFingerprint) {
2356
- throw new Error("Session key not initialized");
2357
- }
2358
- const keypair = await SessionKeyCrypto.decrypt(
2359
- this.encrypted,
2360
- pin,
2361
- this.deviceFingerprint.fingerprint
2362
- );
2363
- const ttl = cacheTTL || this.DEFAULT_CACHE_TTL_MS;
2364
- this.cacheKeypair(keypair, ttl);
2365
- }
2366
- /**
2367
- * Get time remaining until cache expires (in milliseconds)
2368
- * Returns 0 if not cached
2369
- */
2370
- getCacheTimeRemaining() {
2371
- if (!this.isCached() || !this.cacheExpiry) {
2372
- return 0;
2373
- }
2374
- const remaining = this.cacheExpiry - Date.now();
2375
- return Math.max(0, remaining);
2376
- }
2377
- /**
2378
- * Extend cache expiry time
2379
- * Useful to keep session active during user activity
2380
- *
2381
- * @example
2382
- * ```typescript
2383
- * // Extend cache by 15 minutes on each payment
2384
- * await sessionKey.signTransaction(tx, '');
2385
- * sessionKey.extendCache(15 * 60 * 1000);
2386
- * ```
2387
- */
2388
- extendCache(additionalTTL) {
2389
- if (!this.isCached()) {
2390
- throw new Error("Cannot extend cache: no cached keypair");
2391
- }
2392
- this.cacheExpiry += additionalTTL;
2393
- }
2394
- /**
2395
- * Set session key ID after backend creation
2396
- */
2397
- setSessionKeyId(id) {
2398
- this.sessionKeyId = id;
2399
- }
2400
- /**
2401
- * Get session key ID
2402
- */
2403
- getSessionKeyId() {
2404
- if (!this.sessionKeyId) {
2405
- throw new Error("Session key not registered with backend");
2406
- }
2407
- return this.sessionKeyId;
2408
- }
2409
- };
2410
- async function encryptKeypairWithLit(keypair, options) {
2411
- const network = options?.network || "datil";
2412
- const debug = options?.debug || false;
2413
- if (debug) {
2414
- console.log("[Lit] Encrypting keypair with Lit Protocol...");
2415
- }
2416
- try {
2417
- const { LitNodeClient } = await import("@lit-protocol/lit-node-client");
2418
- const litClient = new LitNodeClient({
2419
- litNetwork: network,
2420
- debug
2421
- });
2422
- await litClient.connect();
2423
- if (debug) {
2424
- console.log(`[Lit] Connected to ${network}`);
2425
- }
2426
- try {
2427
- const evmContractConditions = [
2428
- {
2429
- contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
2430
- // USDC on Ethereum
2431
- functionName: "balanceOf",
2432
- functionParams: [":userAddress"],
2433
- functionAbi: {
2434
- constant: true,
2435
- inputs: [{ name: "account", type: "address" }],
2436
- name: "balanceOf",
2437
- outputs: [{ name: "", type: "uint256" }],
2438
- stateMutability: "view",
2439
- type: "function"
2440
- },
2441
- chain: "ethereum",
2442
- returnValueTest: {
2443
- key: "",
2444
- comparator: ">=",
2445
- value: "0"
2446
- }
2447
- }
2448
- ];
2449
- const dataToEncrypt = keypair.secretKey;
2450
- if (debug) {
2451
- console.log("[Lit] Encrypting keypair bytes with evmContractConditions...");
2452
- }
2453
- const encryptResult = await litClient.encrypt({
2454
- evmContractConditions,
2455
- dataToEncrypt
2456
- });
2457
- if (debug) {
2458
- console.log("[Lit] \u2705 Encryption successful");
2459
- }
2460
- return {
2461
- ciphertext: encryptResult.ciphertext,
2462
- dataHash: encryptResult.dataToEncryptHash
2463
- };
2464
- } finally {
2465
- await litClient.disconnect();
2466
- }
2467
- } catch (error) {
2468
- if (debug) {
2469
- console.error("[Lit] \u274C Encryption failed:", error);
2470
- }
2471
- throw new Error(
2472
- `Lit Protocol encryption failed: ${error instanceof Error ? error.message : "Unknown error"}`
2473
- );
2474
- }
2475
- }
2476
-
2477
- // src/aip/session-keys.ts
2478
- var import_web32 = require("@solana/web3.js");
2479
- var SessionKeysAPI = class {
2480
- sessionKeys = /* @__PURE__ */ new Map();
2481
- sessionMetadata = /* @__PURE__ */ new Map();
2482
- requestFn;
2483
- debugMode = false;
2484
- constructor(request) {
2485
- this.requestFn = request;
2486
- }
2487
- /**
2488
- * Enable debug logging
2489
- */
2490
- debug(...args) {
2491
- if (this.debugMode && typeof console !== "undefined") {
2492
- console.log("[ZendFi SessionKeys]", ...args);
2493
- }
2494
- }
2495
- /**
2496
- * Create a new device-bound session key
2497
- *
2498
- * The keypair is generated client-side and encrypted with your PIN.
2499
- * The backend NEVER sees your private key.
2500
- *
2501
- * @param options - Session key configuration
2502
- * @returns Created session key info with optional recovery QR
2503
- *
2504
- * @example
2505
- * ```typescript
2506
- * const result = await zendfi.sessionKeys.create({
2507
- * userWallet: '7xKNH...',
2508
- * agentId: 'shopping-assistant-v1',
2509
- * agentName: 'AI Shopping Assistant',
2510
- * limitUSDC: 100,
2511
- * durationDays: 7,
2512
- * pin: '123456',
2513
- * generateRecoveryQR: true,
2514
- * });
2515
- *
2516
- * console.log(`Session key: ${result.sessionKeyId}`);
2517
- * console.log(`Recovery QR: ${result.recoveryQR}`);
2518
- * ```
2519
- */
2520
- async create(options) {
2521
- if (!options.pin || options.pin.length < 4) {
2522
- throw new Error("PIN must be at least 4 characters");
2523
- }
2524
- const sessionKey = await DeviceBoundSessionKey.create({
2525
- pin: options.pin,
2526
- limitUSDC: options.limitUSDC,
2527
- durationDays: options.durationDays,
2528
- userWallet: options.userWallet,
2529
- generateRecoveryQR: options.generateRecoveryQR
2530
- });
2531
- const encrypted = sessionKey.getEncryptedData();
2532
- let recoveryQR;
2533
- if (options.generateRecoveryQR) {
2534
- const qr = RecoveryQRGenerator.generate(encrypted);
2535
- recoveryQR = RecoveryQRGenerator.encode(qr);
2536
- }
2537
- let litEncryption;
2538
- const enableLit = options.enableLitProtocol !== false;
2539
- if (enableLit) {
2540
- const maxRetries = 3;
2541
- let lastError;
2542
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
2543
- try {
2544
- this.debug(`Encrypting session key with Lit Protocol (attempt ${attempt}/${maxRetries})...`);
2545
- litEncryption = await encryptKeypairWithLit(sessionKey.getKeypair(), {
2546
- network: options.litNetwork || "datil",
2547
- debug: this.debugMode
2548
- });
2549
- this.debug("Lit Protocol encryption successful - autonomous signing enabled");
2550
- break;
2551
- } catch (error) {
2552
- lastError = error instanceof Error ? error : new Error(String(error));
2553
- if (attempt < maxRetries) {
2554
- const delayMs = 500 * Math.pow(2, attempt - 1);
2555
- this.debug(`Lit encryption attempt ${attempt} failed, retrying in ${delayMs}ms...`);
2556
- await new Promise((resolve) => setTimeout(resolve, delayMs));
2557
- } else {
2558
- const errorMsg = lastError.message;
2559
- this.debug("Lit encryption failed after 3 attempts, continuing without autonomous mode:", errorMsg);
2560
- console.warn("[ZendFi SDK] Lit Protocol encryption failed after retries:", errorMsg);
2561
- }
2562
- }
2563
- }
2564
- } else {
2565
- this.debug("\u2139\uFE0F Lit Protocol disabled - autonomous signing not available");
2566
- }
2567
- const request = {
2568
- user_wallet: options.userWallet,
2569
- agent_id: options.agentId,
2570
- agent_name: options.agentName,
2571
- limit_usdc: options.limitUSDC,
2572
- duration_days: options.durationDays,
2573
- encrypted_session_key: encrypted.encryptedData,
2574
- nonce: encrypted.nonce,
2575
- session_public_key: encrypted.publicKey,
2576
- device_fingerprint: sessionKey.getDeviceFingerprint(),
2577
- recovery_qr_data: recoveryQR,
2578
- // Include Lit encryption if available
2579
- lit_encrypted_keypair: litEncryption?.ciphertext,
2580
- lit_data_hash: litEncryption?.dataHash
2581
- };
2582
- const response = await this.requestFn(
2583
- "POST",
2584
- "/api/v1/ai/session-keys/device-bound/create",
2585
- request
2586
- );
2587
- sessionKey.setSessionKeyId(response.session_key_id);
2588
- this.sessionKeys.set(response.session_key_id, sessionKey);
2589
- this.sessionMetadata.set(response.session_key_id, {
2590
- agentId: response.agent_id,
2591
- agentName: response.agent_name
2592
- });
2593
- return {
2594
- sessionKeyId: response.session_key_id,
2595
- agentId: response.agent_id,
2596
- agentName: response.agent_name,
2597
- sessionWallet: response.session_wallet,
2598
- limitUsdc: response.limit_usdc,
2599
- expiresAt: response.expires_at,
2600
- recoveryQR,
2601
- crossAppCompatible: response.cross_app_compatible
2602
- };
2603
- }
2604
- /**
2605
- * Load an existing session key from backend
2606
- *
2607
- * Fetches the encrypted session key and decrypts it with your PIN.
2608
- * Use this when resuming a session on the same device.
2609
- *
2610
- * @param sessionKeyId - UUID of the session key
2611
- * @param pin - PIN to decrypt the session key
2612
- *
2613
- * @example
2614
- * ```typescript
2615
- * // Resume session on same device
2616
- * await zendfi.sessionKeys.load('uuid-of-session-key', '123456');
2617
- *
2618
- * // Now you can make payments
2619
- * await zendfi.sessionKeys.makePayment({...});
2620
- * ```
2621
- */
2622
- async load(sessionKeyId, pin) {
2623
- const deviceInfo = await DeviceFingerprintGenerator.generate();
2624
- const response = await this.requestFn(
2625
- "POST",
2626
- "/api/v1/ai/session-keys/device-bound/get-encrypted",
2627
- {
2628
- session_key_id: sessionKeyId,
2629
- device_fingerprint: deviceInfo.fingerprint
2630
- }
2631
- );
2632
- if (!response.device_fingerprint_valid) {
2633
- throw new Error(
2634
- "Device fingerprint mismatch - this session key was created on a different device. Use recover() to migrate."
2635
- );
2636
- }
2637
- const encrypted = {
2638
- encryptedData: response.encrypted_session_key,
2639
- nonce: response.nonce,
2640
- publicKey: "",
2641
- // Will be populated after decryption
2642
- deviceFingerprint: deviceInfo.fingerprint,
2643
- version: "argon2id-aes256gcm-v1"
2644
- };
2645
- const keypair = await SessionKeyCrypto.decrypt(encrypted, pin, deviceInfo.fingerprint);
2646
- encrypted.publicKey = keypair.publicKey.toBase58();
2647
- const sessionKey = new DeviceBoundSessionKey();
2648
- sessionKey.encrypted = encrypted;
2649
- sessionKey.deviceFingerprint = deviceInfo;
2650
- sessionKey.setSessionKeyId(sessionKeyId);
2651
- this.sessionKeys.set(sessionKeyId, sessionKey);
2652
- }
2653
- /**
2654
- * Unlock a session key for auto-signing
2655
- *
2656
- * After unlocking, payments can be made without entering PIN.
2657
- * The decrypted keypair is cached in memory with a TTL.
2658
- *
2659
- * @param sessionKeyId - UUID of the session key
2660
- * @param pin - PIN to decrypt the session key
2661
- * @param cacheTTL - How long to cache (default: 30 minutes)
2662
- *
2663
- * @example
2664
- * ```typescript
2665
- * // Unlock once
2666
- * await zendfi.sessionKeys.unlock('uuid', '123456');
2667
- *
2668
- * // Make payments instantly (no PIN!)
2669
- * await zendfi.sessionKeys.makePayment({...}); // Instant!
2670
- * await zendfi.sessionKeys.makePayment({...}); // Instant!
2671
- * ```
2672
- */
2673
- async unlock(sessionKeyId, pin, cacheTTL) {
2674
- const sessionKey = this.sessionKeys.get(sessionKeyId);
2675
- if (!sessionKey) {
2676
- await this.load(sessionKeyId, pin);
2677
- const loaded = this.sessionKeys.get(sessionKeyId);
2678
- if (loaded) {
2679
- await loaded.unlockWithPin(pin, cacheTTL);
2680
- }
2681
- return;
2682
- }
2683
- await sessionKey.unlockWithPin(pin, cacheTTL);
2684
- }
2685
- /**
2686
- * Make a payment using a session key
2687
- *
2688
- * If the session key is unlocked (cached), no PIN is needed.
2689
- * Otherwise, you must provide the PIN.
2690
- *
2691
- * @param options - Payment configuration
2692
- * @returns Payment result with signature
2693
- *
2694
- * @example
2695
- * ```typescript
2696
- * // With unlocked session key (no PIN)
2697
- * const result = await zendfi.sessionKeys.makePayment({
2698
- * sessionKeyId: 'uuid',
2699
- * amount: 5.0,
2700
- * recipient: '8xYZA...',
2701
- * description: 'Coffee purchase',
2702
- * });
2703
- *
2704
- * // Or with PIN (one-time)
2705
- * const result = await zendfi.sessionKeys.makePayment({
2706
- * sessionKeyId: 'uuid',
2707
- * amount: 5.0,
2708
- * recipient: '8xYZA...',
2709
- * pin: '123456',
2710
- * });
2711
- * ```
2712
- */
2713
- async makePayment(options) {
2714
- const sessionKey = this.sessionKeys.get(options.sessionKeyId);
2715
- if (!sessionKey) {
2716
- throw new Error(
2717
- `Session key ${options.sessionKeyId} not loaded. Call create() or load() first.`
2718
- );
2719
- }
2720
- const enableAutoSign = options.enableAutoSign !== false;
2721
- const needsPin = !sessionKey.isCached();
2722
- if (needsPin && !options.pin) {
2723
- throw new Error(
2724
- "PIN required: session key not unlocked. Provide PIN or call unlock() first."
2725
- );
2726
- }
2727
- const metadata = this.sessionMetadata.get(options.sessionKeyId);
2728
- if (!metadata) {
2729
- throw new Error(
2730
- `Session key metadata not found for ${options.sessionKeyId}. Was it created in this session?`
2731
- );
2732
- }
2733
- const paymentResponse = await this.requestFn("POST", "/api/v1/ai/smart-payment", {
2734
- agent_id: metadata.agentId,
2735
- // Required by backend
2736
- amount_usd: options.amount,
2737
- user_wallet: sessionKey.getPublicKey(),
2738
- // Session key's wallet (payer)
2739
- token: options.token || "USDC",
2740
- description: options.description,
2741
- session_key_id: options.sessionKeyId
2742
- });
2743
- if (!paymentResponse.requires_signature && paymentResponse.status === "confirmed") {
2744
- return {
2745
- paymentId: paymentResponse.payment_id,
2746
- signature: "",
2747
- status: paymentResponse.status
2748
- };
2749
- }
2750
- if (!paymentResponse.unsigned_transaction) {
2751
- throw new Error("Backend did not return unsigned transaction");
2752
- }
2753
- const transactionBuffer = Buffer.from(paymentResponse.unsigned_transaction, "base64");
2754
- const transaction = import_web32.Transaction.from(transactionBuffer);
2755
- const signedTransaction = await sessionKey.signTransaction(
2756
- transaction,
2757
- options.pin || "",
2758
- enableAutoSign
2759
- );
2760
- const submitResponse = await this.requestFn("POST", `/api/v1/ai/payments/${paymentResponse.payment_id}/submit-signed`, {
2761
- signed_transaction: signedTransaction.serialize().toString("base64")
2762
- });
2763
- return {
2764
- paymentId: paymentResponse.payment_id,
2765
- signature: submitResponse.signature,
2766
- status: submitResponse.status
2767
- };
2768
- }
2769
- /**
2770
- * Get session key status
2771
- *
2772
- * @param sessionKeyId - UUID of the session key
2773
- * @returns Current status including balance and expiry
2774
- */
2775
- async getStatus(sessionKeyId) {
2776
- const response = await this.requestFn("POST", "/api/v1/ai/session-keys/status", {
2777
- session_key_id: sessionKeyId
2778
- });
2779
- return {
2780
- sessionKeyId,
2781
- isActive: response.is_active,
2782
- isApproved: response.is_approved,
2783
- limitUsdc: response.limit_usdc,
2784
- usedAmountUsdc: response.used_amount_usdc,
2785
- remainingUsdc: response.remaining_usdc,
2786
- expiresAt: response.expires_at,
2787
- daysUntilExpiry: response.days_until_expiry
2788
- };
2789
- }
2790
- /**
2791
- * Revoke a session key
2792
- *
2793
- * Permanently deactivates the session key. Cannot be undone.
2794
- *
2795
- * @param sessionKeyId - UUID of the session key to revoke
2796
- */
2797
- async revoke(sessionKeyId) {
2798
- await this.requestFn("POST", "/api/v1/ai/session-keys/revoke", {
2799
- session_key_id: sessionKeyId
2800
- });
2801
- this.sessionKeys.delete(sessionKeyId);
2802
- }
2803
- /**
2804
- * Recover session key on new device
2805
- *
2806
- * Use this when moving to a new device with a recovery QR code.
2807
- *
2808
- * @param options - Recovery configuration
2809
- *
2810
- * @example
2811
- * ```typescript
2812
- * await zendfi.sessionKeys.recover({
2813
- * sessionKeyId: 'uuid',
2814
- * recoveryQR: '{"encryptedSessionKey":"..."}',
2815
- * oldPin: '123456',
2816
- * newPin: '654321',
2817
- * });
2818
- * ```
2819
- */
2820
- async recover(options) {
2821
- const recoveryData = RecoveryQRGenerator.decode(options.recoveryQR);
2822
- const newDeviceInfo = await DeviceFingerprintGenerator.generate();
2823
- const newEncrypted = await RecoveryQRGenerator.reEncryptForNewDevice(
2824
- recoveryData,
2825
- options.oldPin,
2826
- "recovery-mode",
2827
- // Old fingerprint (stored in QR in production)
2828
- options.newPin,
2829
- newDeviceInfo.fingerprint
2830
- );
2831
- await this.requestFn(
2832
- "POST",
2833
- `/api/v1/ai/session-keys/device-bound/${options.sessionKeyId}/recover`,
2834
- {
2835
- recovery_qr_data: options.recoveryQR,
2836
- new_device_fingerprint: newDeviceInfo.fingerprint,
2837
- new_encrypted_session_key: newEncrypted.encryptedData,
2838
- new_nonce: newEncrypted.nonce
2839
- }
2840
- );
2841
- await this.load(options.sessionKeyId, options.newPin);
2842
- }
2843
- /**
2844
- * Clear cached keypair for a session key
2845
- *
2846
- * Use this on logout or when session ends.
2847
- *
2848
- * @param sessionKeyId - UUID of the session key (or all if not specified)
2849
- */
2850
- clearCache(sessionKeyId) {
2851
- if (sessionKeyId) {
2852
- const sessionKey = this.sessionKeys.get(sessionKeyId);
2853
- if (sessionKey) {
2854
- sessionKey.clearCache();
2855
- }
2856
- } else {
2857
- for (const sessionKey of this.sessionKeys.values()) {
2858
- sessionKey.clearCache();
2859
- }
2860
- }
2861
- }
2862
- /**
2863
- * Check if a session key is cached (unlocked)
2864
- *
2865
- * @param sessionKeyId - UUID of the session key
2866
- * @returns True if keypair is cached and auto-signing is enabled
2867
- */
2868
- isCached(sessionKeyId) {
2869
- const sessionKey = this.sessionKeys.get(sessionKeyId);
2870
- return sessionKey?.isCached() || false;
2871
- }
2872
- /**
2873
- * Get time remaining until cache expires
2874
- *
2875
- * @param sessionKeyId - UUID of the session key
2876
- * @returns Milliseconds until cache expires
2877
- */
2878
- getCacheTimeRemaining(sessionKeyId) {
2879
- const sessionKey = this.sessionKeys.get(sessionKeyId);
2880
- return sessionKey?.getCacheTimeRemaining() || 0;
2881
- }
2882
- /**
2883
- * Extend cache expiry time
2884
- *
2885
- * Useful to keep session active during user activity.
2886
- *
2887
- * @param sessionKeyId - UUID of the session key
2888
- * @param additionalTTL - Additional time in milliseconds
2889
- */
2890
- extendCache(sessionKeyId, additionalTTL) {
2891
- const sessionKey = this.sessionKeys.get(sessionKeyId);
2892
- if (sessionKey) {
2893
- sessionKey.extendCache(additionalTTL);
2894
- }
2895
- }
2896
- /**
2897
- * Get all loaded session key IDs
2898
- *
2899
- * @returns Array of session key UUIDs currently loaded
2900
- */
2901
- getLoadedSessionKeys() {
2902
- return Array.from(this.sessionKeys.keys());
2903
- }
2904
- };
2905
-
2906
- // src/client.ts
2907
- var ZendFiClient = class {
2908
- config;
2909
- interceptors;
2910
- // ============================================
2911
- // Agentic Intent Protocol APIs
2912
- // ============================================
2913
- /**
2914
- * Agent API - Manage agent API keys and sessions
2915
- *
2916
- * @example
2917
- * ```typescript
2918
- * // Create an agent API key
2919
- * const agentKey = await zendfi.agent.createKey({
2920
- * name: 'Shopping Assistant',
2921
- * agent_id: 'shopping-assistant-v1',
2922
- * scopes: ['create_payments'],
2923
- * });
2924
- *
2925
- * // Create an agent session
2926
- * const session = await zendfi.agent.createSession({
2927
- * agent_id: 'shopping-assistant-v1',
2928
- * user_wallet: 'Hx7B...abc',
2929
- * limits: { max_per_day: 500 },
2930
- * });
2931
- * ```
2932
- */
2933
- agent;
2934
- /**
2935
- * Payment Intents API - Two-phase payment flow
2936
- *
2937
- * @example
2938
- * ```typescript
2939
- * // Create intent
2940
- * const intent = await zendfi.intents.create({ amount: 99.99 });
2941
- *
2942
- * // Confirm when ready
2943
- * await zendfi.intents.confirm(intent.id, {
2944
- * client_secret: intent.client_secret,
2945
- * customer_wallet: 'Hx7B...abc',
2946
- * });
2947
- * ```
2948
- */
2949
- intents;
2950
- /**
2951
- * Pricing API - PPP and AI-powered pricing
2952
- *
2953
- * @example
2954
- * ```typescript
2955
- * // Get PPP factor for Brazil
2956
- * const factor = await zendfi.pricing.getPPPFactor('BR');
2957
- * const localPrice = 100 * factor.ppp_factor; // $35 for Brazil
2958
- *
2959
- * // Get AI pricing suggestion
2960
- * const suggestion = await zendfi.pricing.getSuggestion({
2961
- * agent_id: 'my-agent',
2962
- * base_price: 100,
2963
- * user_profile: { location_country: 'BR' },
2964
- * });
2965
- * ```
2966
- */
2967
- pricing;
2968
- /**
2969
- * Autonomy API - Enable autonomous agent signing
2970
- *
2971
- * @example
2972
- * ```typescript
2973
- * // Enable autonomous mode for a session key
2974
- * const delegate = await zendfi.autonomy.enable(sessionKeyId, {
2975
- * max_amount_usd: 100,
2976
- * duration_hours: 24,
2977
- * delegation_signature: signature,
2978
- * });
2979
- *
2980
- * // Check status
2981
- * const status = await zendfi.autonomy.getStatus(sessionKeyId);
2982
- * ```
2983
- */
2984
- autonomy;
2985
- /**
2986
- * Smart Payments API - AI-powered payment routing
2987
- *
2988
- * Create intelligent payments that automatically:
2989
- * - Apply PPP discounts based on user location
2990
- * - Use agent sessions when available
2991
- * - Route to optimal payment paths
2992
- *
2993
- * @example
2994
- * ```typescript
2995
- * const payment = await zendfi.smart.create({
2996
- * amount_usd: 99.99,
2997
- * wallet_address: 'Hx7B...abc',
2998
- * merchant_id: 'merch_123',
2999
- * country_code: 'BR', // Apply PPP
3000
- * enable_ppp: true,
3001
- * });
3002
- *
3003
- * console.log(`Original: $${payment.original_amount_usd}`);
3004
- * console.log(`Final: $${payment.final_amount_usd}`);
3005
- * // Original: $99.99
3006
- * // Final: $64.99 (35% PPP discount applied)
3007
- * ```
3008
- */
3009
- smart;
3010
- /**
3011
- * Session Keys API - On-chain funded session keys with PKP identity
3012
- *
3013
- * Session keys are pre-funded wallets with spending limits that enable
3014
- * AI agents to make autonomous payments without user signatures.
3015
- *
3016
- * The flow:
3017
- * 1. Create a session key (user approves spending limit)
3018
- * 2. User signs the approval transaction (one-time)
3019
- * 3. Agent can now make payments up to the limit
3020
- *
3021
- * @example
3022
- * ```typescript
3023
- * // Create session key
3024
- * const key = await zendfi.sessionKeys.create({
3025
- * agent_id: 'shopping-bot',
3026
- * user_wallet: 'Hx7B...abc',
3027
- * max_amount: 100,
3028
- * expiry_hours: 24,
3029
- * });
3030
- *
3031
- * // User signs the approval
3032
- * await zendfi.sessionKeys.submitApproval(key.session_key_id, {
3033
- * signed_transaction: signedTx,
3034
- * });
3035
- *
3036
- * // Check status
3037
- * const status = await zendfi.sessionKeys.getStatus(key.session_key_id);
3038
- * console.log(`Remaining: $${status.remaining_amount}`);
3039
- * ```
3040
- */
3041
- sessionKeys;
3042
- constructor(options) {
3043
- this.config = ConfigLoader.load(options);
3044
- ConfigLoader.validateApiKey(this.config.apiKey);
3045
- this.interceptors = createInterceptors();
3046
- const boundRequest = this.request.bind(this);
3047
- this.agent = new AgentAPI(boundRequest);
3048
- this.intents = new PaymentIntentsAPI(boundRequest);
3049
- this.pricing = new PricingAPI(boundRequest);
3050
- this.autonomy = new AutonomyAPI(boundRequest);
3051
- this.smart = new SmartPaymentsAPI(boundRequest);
3052
- this.sessionKeys = new SessionKeysAPI(boundRequest);
3053
- if (this.config.environment === "development" || this.config.debug) {
3054
- console.log(
3055
- `\u2713 ZendFi SDK initialized in ${this.config.mode} mode (${this.config.mode === "test" ? "devnet" : "mainnet"})`
3056
- );
3057
- if (this.config.debug) {
3058
- console.log("[ZendFi] Debug mode enabled");
3059
- }
3060
- }
3061
- }
3062
- /**
3063
- * Create a new payment
3064
- */
3065
- async createPayment(request) {
3066
- return this.request("POST", "/api/v1/payments", {
3067
- ...request,
3068
- currency: request.currency || "USD",
3069
- token: request.token || "USDC"
3070
- });
3071
- }
3072
- /**
3073
- * Get payment by ID
3074
- */
3075
- async getPayment(paymentId) {
3076
- return this.request("GET", `/api/v1/payments/${paymentId}`);
3077
- }
3078
- /**
3079
- * List all payments with pagination
3080
- */
3081
- async listPayments(request) {
3082
- const params = new URLSearchParams();
3083
- if (request?.page) params.append("page", request.page.toString());
3084
- if (request?.limit) params.append("limit", request.limit.toString());
3085
- if (request?.status) params.append("status", request.status);
3086
- if (request?.from_date) params.append("from_date", request.from_date);
3087
- if (request?.to_date) params.append("to_date", request.to_date);
3088
- const query = params.toString() ? `?${params.toString()}` : "";
3089
- return this.request("GET", `/api/v1/payments${query}`);
3090
- }
3091
- /**
3092
- * Create a subscription plan
3093
- */
3094
- async createSubscriptionPlan(request) {
3095
- return this.request("POST", "/api/v1/subscriptions/plans", {
3096
- ...request,
3097
- currency: request.currency || "USD",
3098
- interval_count: request.interval_count || 1,
3099
- trial_days: request.trial_days || 0
3100
- });
3101
- }
3102
- /**
3103
- * Get subscription plan by ID
3104
- */
3105
- async getSubscriptionPlan(planId) {
3106
- return this.request("GET", `/api/v1/subscriptions/plans/${planId}`);
3107
- }
3108
- /**
3109
- * Create a subscription
3110
- */
3111
- async createSubscription(request) {
3112
- return this.request("POST", "/api/v1/subscriptions", request);
3113
- }
3114
- /**
3115
- * Get subscription by ID
3116
- */
3117
- async getSubscription(subscriptionId) {
3118
- return this.request("GET", `/api/v1/subscriptions/${subscriptionId}`);
3119
- }
3120
- /**
3121
- * Cancel a subscription
3122
- */
3123
- async cancelSubscription(subscriptionId) {
3124
- return this.request(
3125
- "POST",
3126
- `/api/v1/subscriptions/${subscriptionId}/cancel`
3127
- );
3128
- }
3129
- /**
3130
- * Create a payment link (shareable checkout URL)
3131
- */
3132
- async createPaymentLink(request) {
3133
- const response = await this.request("POST", "/api/v1/payment-links", {
3134
- ...request,
3135
- currency: request.currency || "USD",
3136
- token: request.token || "USDC",
3137
- onramp: request.onramp || false
3138
- });
3139
- return {
3140
- ...response,
3141
- url: response.hosted_page_url
3142
- };
3143
- }
3144
- /**
3145
- * Get payment link by link code
3146
- */
3147
- async getPaymentLink(linkCode) {
3148
- const response = await this.request("GET", `/api/v1/payment-links/${linkCode}`);
3149
- return {
3150
- ...response,
3151
- url: response.hosted_page_url
3152
- };
3153
- }
3154
- /**
3155
- * List all payment links for the authenticated merchant
3156
- */
3157
- async listPaymentLinks() {
3158
- const response = await this.request("GET", "/api/v1/payment-links");
3159
- return response.map((link) => ({
3160
- ...link,
3161
- url: link.hosted_page_url
3162
- }));
3163
- }
3164
- /**
3165
- * Create an installment plan
3166
- * Split a purchase into multiple scheduled payments
3167
- */
3168
- async createInstallmentPlan(request) {
3169
- const response = await this.request(
3170
- "POST",
3171
- "/api/v1/installment-plans",
3172
- request
3173
- );
3174
- return {
3175
- id: response.plan_id,
3176
- plan_id: response.plan_id,
3177
- status: response.status
3178
- };
3179
- }
3180
- /**
3181
- * Get installment plan by ID
3182
- */
3183
- async getInstallmentPlan(planId) {
3184
- return this.request("GET", `/api/v1/installment-plans/${planId}`);
647
+ async getInstallmentPlan(planId) {
648
+ return this.request("GET", `/api/v1/installment-plans/${planId}`);
3185
649
  }
3186
650
  /**
3187
651
  * List all installment plans for merchant
@@ -3203,63 +667,14 @@ var ZendFiClient = class {
3203
667
  );
3204
668
  }
3205
669
  /**
3206
- * Cancel an installment plan
3207
- */
3208
- async cancelInstallmentPlan(planId) {
3209
- return this.request(
3210
- "POST",
3211
- `/api/v1/installment-plans/${planId}/cancel`
3212
- );
3213
- }
3214
- /**
3215
- * Create an escrow transaction
3216
- * Hold funds until conditions are met
3217
- */
3218
- async createEscrow(request) {
3219
- return this.request("POST", "/api/v1/escrows", {
3220
- ...request,
3221
- currency: request.currency || "USD",
3222
- token: request.token || "USDC"
3223
- });
3224
- }
3225
- /**
3226
- * Get escrow by ID
3227
- */
3228
- async getEscrow(escrowId) {
3229
- return this.request("GET", `/api/v1/escrows/${escrowId}`);
3230
- }
3231
- /**
3232
- * List all escrows for merchant
3233
- */
3234
- async listEscrows(params) {
3235
- const query = new URLSearchParams();
3236
- if (params?.limit) query.append("limit", params.limit.toString());
3237
- if (params?.offset) query.append("offset", params.offset.toString());
3238
- const queryString = query.toString() ? `?${query.toString()}` : "";
3239
- return this.request("GET", `/api/v1/escrows${queryString}`);
3240
- }
3241
- /**
3242
- * Approve escrow release to seller
670
+ * Cancel an installment plan
3243
671
  */
3244
- async approveEscrow(escrowId, request) {
672
+ async cancelInstallmentPlan(planId) {
3245
673
  return this.request(
3246
674
  "POST",
3247
- `/api/v1/escrows/${escrowId}/approve`,
3248
- request
675
+ `/api/v1/installment-plans/${planId}/cancel`
3249
676
  );
3250
677
  }
3251
- /**
3252
- * Refund escrow to buyer
3253
- */
3254
- async refundEscrow(escrowId, request) {
3255
- return this.request("POST", `/api/v1/escrows/${escrowId}/refund`, request);
3256
- }
3257
- /**
3258
- * Raise a dispute for an escrow
3259
- */
3260
- async disputeEscrow(escrowId, request) {
3261
- return this.request("POST", `/api/v1/escrows/${escrowId}/dispute`, request);
3262
- }
3263
678
  /**
3264
679
  * Create an invoice
3265
680
  */
@@ -3287,54 +702,6 @@ var ZendFiClient = class {
3287
702
  async sendInvoice(invoiceId) {
3288
703
  return this.request("POST", `/api/v1/invoices/${invoiceId}/send`);
3289
704
  }
3290
- // ============================================
3291
- // Agentic Intent Protocol - Smart Payments
3292
- // ============================================
3293
- /**
3294
- * Execute an AI-powered smart payment
3295
- *
3296
- * Smart payments combine multiple features:
3297
- * - Automatic PPP pricing adjustments
3298
- * - Gasless transaction detection
3299
- * - Instant settlement options
3300
- * - Escrow integration
3301
- * - Receipt generation
3302
- *
3303
- * @param request - Smart payment configuration
3304
- * @returns Payment result with status and receipt
3305
- *
3306
- * @example
3307
- * ```typescript
3308
- * const result = await zendfi.smartPayment({
3309
- * agent_id: 'shopping-assistant',
3310
- * user_wallet: 'Hx7B...abc',
3311
- * amount_usd: 50,
3312
- * auto_detect_gasless: true,
3313
- * description: 'Premium subscription',
3314
- * });
3315
- *
3316
- * if (result.requires_signature) {
3317
- * // Device-bound flow: user needs to sign
3318
- * console.log('Sign and submit:', result.submit_url);
3319
- * } else {
3320
- * // Auto-signed
3321
- * console.log('Payment complete:', result.transaction_signature);
3322
- * }
3323
- * ```
3324
- */
3325
- async smartPayment(request) {
3326
- return this.smart.execute(request);
3327
- }
3328
- /**
3329
- * Submit a signed transaction for device-bound smart payment
3330
- *
3331
- * @param paymentId - UUID of the payment
3332
- * @param signedTransaction - Base64 encoded signed transaction
3333
- * @returns Updated payment response
3334
- */
3335
- async submitSignedPayment(paymentId, signedTransaction) {
3336
- return this.smart.submitSigned(paymentId, signedTransaction);
3337
- }
3338
705
  /**
3339
706
  * Verify webhook signature using HMAC-SHA256
3340
707
  *
@@ -3628,6 +995,8 @@ var ZendFiEmbeddedCheckout = class {
3628
995
  mounted = false;
3629
996
  paymentProcessed = false;
3630
997
  // Prevent duplicate success callbacks
998
+ // Bank payment state
999
+ bankPaymentState = {};
3631
1000
  constructor(config) {
3632
1001
  this.config = {
3633
1002
  linkCode: config.linkCode || "",
@@ -3646,7 +1015,8 @@ var ZendFiEmbeddedCheckout = class {
3646
1015
  paymentMethods: config.paymentMethods || {
3647
1016
  walletConnect: true,
3648
1017
  qrCode: true,
3649
- solanaWallet: true
1018
+ solanaWallet: true,
1019
+ bank: true
3650
1020
  }
3651
1021
  };
3652
1022
  if (!this.config.linkCode && !this.config.paymentId) {
@@ -3909,12 +1279,14 @@ var ZendFiEmbeddedCheckout = class {
3909
1279
  */
3910
1280
  renderPaymentMethods() {
3911
1281
  if (!this.checkoutData) return "";
1282
+ const showBank = this.config.paymentMethods.bank && this.checkoutData.onramp;
3912
1283
  return `
3913
1284
  <div class="zendfi-payment-methods" style="padding: 24px;">
3914
1285
  <h3 style="margin: 0 0 16px 0; font-size: 16px; font-weight: 600; color: #1f2937;">
3915
1286
  Payment Methods
3916
1287
  </h3>
3917
1288
 
1289
+ ${showBank ? this.renderBankMethod() : ""}
3918
1290
  ${this.config.paymentMethods.qrCode ? this.renderQRCodeMethod() : ""}
3919
1291
  ${this.config.paymentMethods.solanaWallet ? this.renderWalletMethod() : ""}
3920
1292
  ${this.config.paymentMethods.walletConnect ? this.renderWalletConnectMethod() : ""}
@@ -3981,6 +1353,142 @@ var ZendFiEmbeddedCheckout = class {
3981
1353
  </div>
3982
1354
  `;
3983
1355
  }
1356
+ /**
1357
+ * Render bank payment method (PAJ Ramp onramp)
1358
+ */
1359
+ renderBankMethod() {
1360
+ if (!this.checkoutData) return "";
1361
+ const isEmailStep = !this.bankPaymentState.orderId;
1362
+ const isBankDetailsStep = !!this.bankPaymentState.bankDetails;
1363
+ return `
1364
+ <div class="zendfi-payment-method" style="padding: 16px; border: 2px solid #10b981; border-radius: 12px; margin-bottom: 16px; background: #f0fdf4;">
1365
+ <div style="margin-bottom: 12px;">
1366
+ <h4 style="margin: 0 0 4px 0; font-size: 16px; font-weight: 600; color: #065f46; display: flex; align-items: center;">
1367
+ \u{1F3E6} Pay with Bank Transfer (Nigeria)
1368
+ </h4>
1369
+ <p style="margin: 0; font-size: 12px; color: #047857;">
1370
+ Pay with your Nigerian bank account \u2022 Instant confirmation
1371
+ </p>
1372
+ </div>
1373
+
1374
+ ${isEmailStep ? this.renderBankEmailStep() : ""}
1375
+ ${isBankDetailsStep ? this.renderBankDetailsStep() : ""}
1376
+ </div>
1377
+ `;
1378
+ }
1379
+ /**
1380
+ * Render bank email/OTP step
1381
+ */
1382
+ renderBankEmailStep() {
1383
+ return `
1384
+ <div id="zendfi-bank-email-step">
1385
+ <div style="margin-bottom: 12px;">
1386
+ <label style="display: block; font-size: 14px; color: #374151; margin-bottom: 4px; font-weight: 500;">
1387
+ Email Address
1388
+ </label>
1389
+ <input
1390
+ type="email"
1391
+ id="zendfi-bank-email"
1392
+ placeholder="your@email.com"
1393
+ style="width: 100%; padding: 10px; border: 1px solid #d1d5db; border-radius: 8px; font-size: 14px;"
1394
+ />
1395
+ </div>
1396
+
1397
+ <div id="zendfi-bank-otp-container" style="display: none; margin-bottom: 12px;">
1398
+ <label style="display: block; font-size: 14px; color: #374151; margin-bottom: 4px; font-weight: 500;">
1399
+ Verification Code
1400
+ </label>
1401
+ <input
1402
+ type="text"
1403
+ id="zendfi-bank-otp"
1404
+ placeholder="Enter 4-digit code"
1405
+ maxlength="4"
1406
+ style="width: 100%; padding: 10px; border: 1px solid #d1d5db; border-radius: 8px; font-size: 14px;"
1407
+ />
1408
+ <p style="font-size: 12px; color: #6b7280; margin-top: 4px;">
1409
+ Check your email for the verification code
1410
+ </p>
1411
+ </div>
1412
+
1413
+ <button
1414
+ id="zendfi-bank-submit"
1415
+ style="width: 100%; padding: 12px; background: #10b981; color: white; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s;"
1416
+ onmouseover="this.style.background='#059669';"
1417
+ onmouseout="this.style.background='#10b981';"
1418
+ >
1419
+ Send Verification Code
1420
+ </button>
1421
+
1422
+ <div id="zendfi-bank-error" style="display: none; margin-top: 12px; padding: 12px; background: #fee2e2; border: 1px solid #fecaca; border-radius: 8px; color: #991b1b; font-size: 14px;"></div>
1423
+ </div>
1424
+ `;
1425
+ }
1426
+ /**
1427
+ * Render bank details step
1428
+ */
1429
+ renderBankDetailsStep() {
1430
+ if (!this.bankPaymentState.bankDetails) return "";
1431
+ const { accountNumber, accountName, bankName, amount } = this.bankPaymentState.bankDetails;
1432
+ return `
1433
+ <div id="zendfi-bank-details-step">
1434
+ <div style="background: white; border: 1px solid #d1d5db; border-radius: 8px; padding: 16px; margin-bottom: 16px;">
1435
+ <h5 style="margin: 0 0 12px 0; font-size: 14px; font-weight: 600; color: #374151;">
1436
+ \u{1F4CB} Transfer to this account:
1437
+ </h5>
1438
+
1439
+ <div style="margin-bottom: 8px;">
1440
+ <div style="font-size: 12px; color: #6b7280; margin-bottom: 2px;">Bank Name</div>
1441
+ <div style="font-weight: 600; color: #111827;">${bankName}</div>
1442
+ </div>
1443
+
1444
+ <div style="margin-bottom: 8px;">
1445
+ <div style="font-size: 12px; color: #6b7280; margin-bottom: 2px;">Account Number</div>
1446
+ <div style="font-weight: 600; color: #111827; font-size: 18px; display: flex; align-items: center; gap: 8px;">
1447
+ <span id="zendfi-bank-account">${accountNumber}</span>
1448
+ <button
1449
+ id="zendfi-copy-account"
1450
+ style="padding: 2px 6px; font-size: 11px; background: #f3f4f6; border: 1px solid #d1d5db; border-radius: 4px; cursor: pointer;"
1451
+ >
1452
+ Copy
1453
+ </button>
1454
+ </div>
1455
+ </div>
1456
+
1457
+ <div style="margin-bottom: 8px;">
1458
+ <div style="font-size: 12px; color: #6b7280; margin-bottom: 2px;">Account Name</div>
1459
+ <div style="font-weight: 600; color: #111827;">${accountName}</div>
1460
+ </div>
1461
+
1462
+ <div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #e5e7eb;">
1463
+ <div style="font-size: 12px; color: #6b7280; margin-bottom: 2px;">Amount to Send</div>
1464
+ <div style="font-size: 24px; font-weight: 700; color: #8866ff;">${amount.toFixed(2)} NGN</div>
1465
+ </div>
1466
+ </div>
1467
+
1468
+ <div style="background: #fef3c7; border: 1px solid #fcd34d; border-radius: 8px; padding: 12px; margin-bottom: 16px;">
1469
+ <p style="margin: 0; font-size: 12px; color: #92400e;">
1470
+ <strong>Important:</strong> Transfer the exact amount shown above. Payment will auto-complete in 10-30 seconds.
1471
+ </p>
1472
+ </div>
1473
+
1474
+ <div id="zendfi-bank-status" style="text-align: center; padding: 16px;">
1475
+ <div class="zendfi-spinner" style="margin: 0 auto 8px;"></div>
1476
+ <div style="color: #6b7280; font-size: 14px;" id="zendfi-bank-status-text">
1477
+ Waiting for bank transfer... (<span id="zendfi-bank-timer">0:00</span>)
1478
+ </div>
1479
+ <div style="font-size: 12px; color: #9ca3af; margin-top: 4px;">
1480
+ \u23F1\uFE0F Usually takes 10-30 seconds
1481
+ </div>
1482
+ <button
1483
+ id="zendfi-refresh-status"
1484
+ style="margin-top: 12px; padding: 8px 16px; background: white; border: 1px solid #e5e7eb; border-radius: 8px; cursor: pointer; color: #6b7280; font-size: 14px;"
1485
+ >
1486
+ \u{1F504} Refresh Status
1487
+ </button>
1488
+ </div>
1489
+ </div>
1490
+ `;
1491
+ }
3984
1492
  /**
3985
1493
  * Render footer
3986
1494
  */
@@ -4053,6 +1561,29 @@ var ZendFiEmbeddedCheckout = class {
4053
1561
  if (walletConnectBtn) {
4054
1562
  walletConnectBtn.addEventListener("click", () => this.handleWalletConnectScan());
4055
1563
  }
1564
+ const bankSubmitBtn = document.getElementById("zendfi-bank-submit");
1565
+ if (bankSubmitBtn) {
1566
+ bankSubmitBtn.addEventListener("click", () => this.handleBankSubmit());
1567
+ }
1568
+ const copyAccountBtn = document.getElementById("zendfi-copy-account");
1569
+ if (copyAccountBtn) {
1570
+ copyAccountBtn.addEventListener("click", () => {
1571
+ const accountNum = document.getElementById("zendfi-bank-account")?.textContent || "";
1572
+ navigator.clipboard.writeText(accountNum);
1573
+ copyAccountBtn.textContent = "Copied!";
1574
+ copyAccountBtn.style.background = "#15803D";
1575
+ copyAccountBtn.style.color = "white";
1576
+ setTimeout(() => {
1577
+ copyAccountBtn.textContent = "Copy";
1578
+ copyAccountBtn.style.background = "";
1579
+ copyAccountBtn.style.color = "";
1580
+ }, 2e3);
1581
+ });
1582
+ }
1583
+ const refreshBtn = document.getElementById("zendfi-refresh-status");
1584
+ if (refreshBtn) {
1585
+ refreshBtn.addEventListener("click", () => this.refreshBankStatus());
1586
+ }
4056
1587
  }
4057
1588
  /**
4058
1589
  * Handle wallet connection and payment
@@ -4289,6 +1820,158 @@ var ZendFiEmbeddedCheckout = class {
4289
1820
  getErrorStyles() {
4290
1821
  return this.getLoadingStyles();
4291
1822
  }
1823
+ /**
1824
+ * Handle bank payment submit (send OTP or verify OTP)
1825
+ */
1826
+ async handleBankSubmit() {
1827
+ const submitBtn = document.getElementById("zendfi-bank-submit");
1828
+ const errorDiv = document.getElementById("zendfi-bank-error");
1829
+ const emailInput = document.getElementById("zendfi-bank-email");
1830
+ const otpContainer = document.getElementById("zendfi-bank-otp-container");
1831
+ const otpInput = document.getElementById("zendfi-bank-otp");
1832
+ if (!submitBtn || !errorDiv || !emailInput) return;
1833
+ errorDiv.style.display = "none";
1834
+ submitBtn.disabled = true;
1835
+ try {
1836
+ if (otpContainer && otpContainer.style.display !== "none" && otpInput) {
1837
+ const otp = otpInput.value.trim();
1838
+ if (otp.length !== 4) {
1839
+ throw new Error("Please enter the 4-digit verification code");
1840
+ }
1841
+ submitBtn.textContent = "Creating order...";
1842
+ const response = await fetch(`${this.config.apiUrl}/api/v1/onramp/create-order`, {
1843
+ method: "POST",
1844
+ headers: { "Content-Type": "application/json" },
1845
+ body: JSON.stringify({
1846
+ customer_email: this.bankPaymentState.customerEmail,
1847
+ otp,
1848
+ fiat_amount: this.checkoutData.amount_usd,
1849
+ currency: this.checkoutData.currency,
1850
+ payment_link_id: this.checkoutData.payment_link_id
1851
+ })
1852
+ });
1853
+ if (!response.ok) {
1854
+ const error = await response.json();
1855
+ throw new Error(error.error || "Failed to create order");
1856
+ }
1857
+ const orderData = await response.json();
1858
+ this.bankPaymentState.orderId = orderData.order_id;
1859
+ this.bankPaymentState.bankDetails = {
1860
+ accountNumber: orderData.bank_account_number,
1861
+ accountName: orderData.bank_account_name,
1862
+ bankName: orderData.bank_name,
1863
+ amount: orderData.fiat_amount
1864
+ };
1865
+ this.render();
1866
+ this.startBankStatusPolling();
1867
+ } else {
1868
+ const email = emailInput.value.trim();
1869
+ if (!email || !email.includes("@")) {
1870
+ throw new Error("Please enter a valid email address");
1871
+ }
1872
+ this.bankPaymentState.customerEmail = email;
1873
+ submitBtn.textContent = "Sending code...";
1874
+ const response = await fetch(`${this.config.apiUrl}/api/v1/onramp/initiate`, {
1875
+ method: "POST",
1876
+ headers: { "Content-Type": "application/json" },
1877
+ body: JSON.stringify({
1878
+ customer_email: email,
1879
+ fiat_amount: this.checkoutData.amount_usd,
1880
+ payment_link_id: this.checkoutData.payment_link_id
1881
+ })
1882
+ });
1883
+ if (!response.ok) {
1884
+ const error = await response.json();
1885
+ throw new Error(error.error || "Failed to send verification code");
1886
+ }
1887
+ if (otpContainer) {
1888
+ otpContainer.style.display = "block";
1889
+ }
1890
+ emailInput.disabled = true;
1891
+ submitBtn.textContent = "Verify Code";
1892
+ }
1893
+ } catch (error) {
1894
+ errorDiv.textContent = error.message;
1895
+ errorDiv.style.display = "block";
1896
+ } finally {
1897
+ submitBtn.disabled = false;
1898
+ }
1899
+ }
1900
+ /**
1901
+ * Start polling bank payment status
1902
+ */
1903
+ startBankStatusPolling() {
1904
+ if (!this.bankPaymentState.orderId) return;
1905
+ let startTime = Date.now();
1906
+ const timerInterval = setInterval(() => {
1907
+ const elapsed = Math.floor((Date.now() - startTime) / 1e3);
1908
+ const minutes = Math.floor(elapsed / 60);
1909
+ const seconds = elapsed % 60;
1910
+ const timerEl = document.getElementById("zendfi-bank-timer");
1911
+ if (timerEl) {
1912
+ timerEl.textContent = `${minutes}:${seconds.toString().padStart(2, "0")}`;
1913
+ }
1914
+ }, 1e3);
1915
+ const pollInterval = setInterval(async () => {
1916
+ await this.checkBankPaymentStatus();
1917
+ }, 5e3);
1918
+ this.bankPaymentState.pollingInterval = pollInterval;
1919
+ this.bankPaymentState.timerInterval = timerInterval;
1920
+ }
1921
+ /**
1922
+ * Check bank payment status
1923
+ */
1924
+ async checkBankPaymentStatus() {
1925
+ if (!this.bankPaymentState.orderId) return;
1926
+ try {
1927
+ const response = await fetch(
1928
+ `${this.config.apiUrl}/api/v1/onramp/orders/${this.bankPaymentState.orderId}`
1929
+ );
1930
+ if (!response.ok) return;
1931
+ const order = await response.json();
1932
+ if (order.status === "COMPLETED" || order.status === "completed") {
1933
+ this.handleBankPaymentSuccess(order);
1934
+ }
1935
+ } catch (error) {
1936
+ console.error("Failed to check bank payment status:", error);
1937
+ }
1938
+ }
1939
+ /**
1940
+ * Handle successful bank payment
1941
+ */
1942
+ handleBankPaymentSuccess(order) {
1943
+ if (this.bankPaymentState.pollingInterval) {
1944
+ clearInterval(this.bankPaymentState.pollingInterval);
1945
+ }
1946
+ if (this.bankPaymentState.timerInterval) {
1947
+ clearInterval(this.bankPaymentState.timerInterval);
1948
+ }
1949
+ this.renderSuccess();
1950
+ if (!this.paymentProcessed) {
1951
+ this.paymentProcessed = true;
1952
+ this.config.onSuccess({
1953
+ paymentId: this.checkoutData.payment_id,
1954
+ transactionSignature: order.paj_order_id,
1955
+ amount: order.token_amount,
1956
+ token: this.checkoutData.token,
1957
+ merchantName: this.checkoutData.merchant_name
1958
+ });
1959
+ }
1960
+ }
1961
+ /**
1962
+ * Manually refresh bank payment status
1963
+ */
1964
+ async refreshBankStatus() {
1965
+ const refreshBtn = document.getElementById("zendfi-refresh-status");
1966
+ if (!refreshBtn) return;
1967
+ refreshBtn.disabled = true;
1968
+ refreshBtn.textContent = "\u{1F504} Checking...";
1969
+ await this.checkBankPaymentStatus();
1970
+ setTimeout(() => {
1971
+ refreshBtn.disabled = false;
1972
+ refreshBtn.textContent = "\u{1F504} Refresh Status";
1973
+ }, 1e3);
1974
+ }
4292
1975
  };
4293
1976
  if (typeof window !== "undefined") {
4294
1977
  window.ZendFiEmbeddedCheckout = ZendFiEmbeddedCheckout;
@@ -4424,552 +2107,16 @@ async function processWebhook(a, b, c) {
4424
2107
  checkDuplicate: cfg.checkDuplicate,
4425
2108
  markProcessed: cfg.markProcessed
4426
2109
  };
4427
- return await processPayload(payload, handlers, fullConfig);
4428
- } catch (err) {
4429
- return {
4430
- success: false,
4431
- processed: false,
4432
- error: err.message,
4433
- statusCode: 500
4434
- };
4435
- }
4436
- }
4437
-
4438
- // src/lit-crypto-signer.ts
4439
- var SPENDING_LIMIT_ACTION_CID = "QmXXunoMeNhXhnr4onzBuvnMzDqH8rf1qdM94RKXayypX3";
4440
- var LitCryptoSigner = class {
4441
- config;
4442
- litNodeClient = null;
4443
- connected = false;
4444
- constructor(config = {}) {
4445
- this.config = {
4446
- network: config.network || "datil-dev",
4447
- apiEndpoint: config.apiEndpoint || "https://api.zendfi.tech",
4448
- apiKey: config.apiKey || "",
4449
- debug: config.debug || false
4450
- };
4451
- }
4452
- async connect() {
4453
- if (this.connected && this.litNodeClient) {
4454
- return;
4455
- }
4456
- this.log("Connecting to Lit Protocol network:", this.config.network);
4457
- const { LitNodeClient } = await import("@lit-protocol/lit-node-client");
4458
- this.litNodeClient = new LitNodeClient({
4459
- litNetwork: this.config.network,
4460
- debug: this.config.debug
4461
- });
4462
- await this.litNodeClient.connect();
4463
- this.connected = true;
4464
- this.log("Connected to Lit Protocol");
4465
- }
4466
- async disconnect() {
4467
- if (this.litNodeClient) {
4468
- await this.litNodeClient.disconnect();
4469
- this.litNodeClient = null;
4470
- this.connected = false;
4471
- }
4472
- }
4473
- async signPayment(params) {
4474
- if (!this.connected || !this.litNodeClient) {
4475
- throw new Error("Not connected to Lit Protocol. Call connect() first.");
4476
- }
4477
- this.log("Signing payment with Lit Protocol");
4478
- this.log(" Session:", params.sessionId);
4479
- this.log(" Amount: $" + params.amountUsd);
4480
- try {
4481
- let sessionSigs = params.sessionSigs;
4482
- if (!sessionSigs) {
4483
- sessionSigs = await this.getSessionSigs(params.pkpPublicKey);
4484
- }
4485
- const result = await this.litNodeClient.executeJs({
4486
- ipfsId: SPENDING_LIMIT_ACTION_CID,
4487
- sessionSigs,
4488
- jsParams: {
4489
- sessionId: params.sessionId,
4490
- requestedAmountUsd: params.amountUsd,
4491
- merchantId: params.merchantId,
4492
- transactionToSign: params.transactionToSign,
4493
- apiEndpoint: this.config.apiEndpoint,
4494
- apiKey: this.config.apiKey,
4495
- pkpPublicKey: params.pkpPublicKey
4496
- }
4497
- });
4498
- this.log("Lit Action result:", result);
4499
- const response = JSON.parse(result.response);
4500
- return {
4501
- success: response.success,
4502
- signature: response.signature,
4503
- publicKey: response.publicKey,
4504
- recid: response.recid,
4505
- sessionId: response.session_id,
4506
- amountUsd: response.amount_usd,
4507
- remainingBudget: response.remaining_budget,
4508
- cryptoEnforced: response.crypto_enforced ?? true,
4509
- error: response.error,
4510
- code: response.code,
4511
- currentSpent: response.current_spent,
4512
- limit: response.limit,
4513
- remaining: response.remaining
4514
- };
4515
- } catch (error) {
4516
- this.log("Lit signing error:", error);
4517
- return {
4518
- success: false,
4519
- cryptoEnforced: true,
4520
- error: error instanceof Error ? error.message : "Unknown error",
4521
- code: "LIT_ERROR"
4522
- };
4523
- }
4524
- }
4525
- async getSessionSigs(pkpPublicKey) {
4526
- this.log("Generating Lit session signatures");
4527
- if (typeof window !== "undefined" && window.ethereum) {
4528
- const { ethers } = await import("ethers");
4529
- const provider = new ethers.BrowserProvider(window.ethereum);
4530
- const signer = await provider.getSigner();
4531
- const { LitAbility, LitPKPResource } = await import("@lit-protocol/auth-helpers");
4532
- const sessionSigs = await this.litNodeClient.getSessionSigs({
4533
- pkpPublicKey,
4534
- chain: "ethereum",
4535
- expiration: new Date(Date.now() + 1e3 * 60 * 10).toISOString(),
4536
- resourceAbilityRequests: [
4537
- {
4538
- resource: new LitPKPResource("*"),
4539
- ability: LitAbility.PKPSigning
4540
- }
4541
- ],
4542
- authNeededCallback: async (params) => {
4543
- const message = params.message;
4544
- const signature = await signer.signMessage(message);
4545
- return {
4546
- sig: signature,
4547
- derivedVia: "web3.eth.personal.sign",
4548
- signedMessage: message,
4549
- address: await signer.getAddress()
4550
- };
4551
- }
4552
- });
4553
- return sessionSigs;
4554
- }
4555
- throw new Error(
4556
- "No wallet available. In browser, ensure MetaMask or similar is connected. In Node.js, pass pre-generated sessionSigs to signPayment()."
4557
- );
4558
- }
4559
- log(...args) {
4560
- if (this.config.debug) {
4561
- console.log("[LitCryptoSigner]", ...args);
4562
- }
4563
- }
4564
- };
4565
- function requiresLitSigning(session) {
4566
- return session.crypto_enforced === true || session.mint_pkp === true;
4567
- }
4568
- function encodeTransactionForLit(transaction) {
4569
- const bytes = transaction instanceof Uint8Array ? transaction : new Uint8Array(transaction);
4570
- return btoa(String.fromCharCode(...bytes));
4571
- }
4572
- function decodeSignatureFromLit(result) {
4573
- if (!result.success || !result.signature) {
4574
- return null;
4575
- }
4576
- const hex = result.signature.startsWith("0x") ? result.signature.slice(2) : result.signature;
4577
- const bytes = new Uint8Array(hex.length / 2);
4578
- for (let i = 0; i < hex.length; i += 2) {
4579
- bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
4580
- }
4581
- return bytes;
4582
- }
4583
-
4584
- // src/helpers/index.ts
4585
- init_cache();
4586
-
4587
- // src/helpers/ai.ts
4588
- var PaymentIntentParser = class {
4589
- /**
4590
- * Parse natural language into structured intent
4591
- */
4592
- static parse(text) {
4593
- if (!text || typeof text !== "string") return null;
4594
- const lowerText = text.toLowerCase().trim();
4595
- let action = "chat_only";
4596
- let confidence = 0;
4597
- if (this.containsPaymentKeywords(lowerText)) {
4598
- action = "payment";
4599
- confidence = 0.7;
4600
- const amount = this.extractAmount(lowerText);
4601
- const description = this.extractDescription(lowerText);
4602
- if (amount && description) {
4603
- confidence = 0.9;
4604
- return { action, amount, description, confidence, rawText: text };
4605
- }
4606
- if (amount) {
4607
- confidence = 0.8;
4608
- return { action, amount, confidence, rawText: text };
4609
- }
4610
- }
4611
- if (this.containsSessionKeywords(lowerText)) {
4612
- action = "create_session";
4613
- confidence = 0.8;
4614
- const amount = this.extractAmount(lowerText);
4615
- return { action, amount, confidence, rawText: text, description: "Session key budget" };
4616
- }
4617
- if (this.containsStatusKeywords(lowerText)) {
4618
- action = lowerText.includes("session") || lowerText.includes("key") ? "check_status" : "check_balance";
4619
- confidence = 0.9;
4620
- return { action, confidence, rawText: text };
4621
- }
4622
- if (this.containsTopUpKeywords(lowerText)) {
4623
- action = "topup";
4624
- confidence = 0.8;
4625
- const amount = this.extractAmount(lowerText);
4626
- return { action, amount, confidence, rawText: text };
4627
- }
4628
- if (this.containsRevokeKeywords(lowerText)) {
4629
- action = "revoke";
4630
- confidence = 0.9;
4631
- return { action, confidence, rawText: text };
4632
- }
4633
- if (this.containsAutonomyKeywords(lowerText)) {
4634
- action = "enable_autonomy";
4635
- confidence = 0.8;
4636
- const amount = this.extractAmount(lowerText);
4637
- return { action, amount, confidence, rawText: text, description: "Autonomous delegate limit" };
4638
- }
4639
- return null;
4640
- }
4641
- /**
4642
- * Generate system prompt for AI models
4643
- */
4644
- static generateSystemPrompt(capabilities = {}) {
4645
- const enabledFeatures = Object.entries({
4646
- createPayment: capabilities.createPayment !== false,
4647
- createSessionKey: capabilities.createSessionKey !== false,
4648
- checkBalance: capabilities.checkBalance !== false,
4649
- checkStatus: capabilities.checkStatus !== false,
4650
- topUpSession: capabilities.topUpSession !== false,
4651
- revokeSession: capabilities.revokeSession !== false,
4652
- enableAutonomy: capabilities.enableAutonomy !== false
4653
- }).filter(([_, enabled]) => enabled).map(([feature]) => feature);
4654
- return `You are a ZendFi payment assistant. You can help users with crypto payments on Solana.
4655
-
4656
- Available Actions:
4657
- ${enabledFeatures.includes("createPayment") ? '- Make payments (e.g., "Buy coffee for $5", "Send $20 to merchant")' : ""}
4658
- ${enabledFeatures.includes("createSessionKey") ? '- Create session keys (e.g., "Create a session key with $100 budget")' : ""}
4659
- ${enabledFeatures.includes("checkBalance") ? `- Check balances (e.g., "What's my balance?", "How much do I have?")` : ""}
4660
- ${enabledFeatures.includes("checkStatus") ? '- Check session status (e.g., "Session key status", "How much is left?")' : ""}
4661
- ${enabledFeatures.includes("topUpSession") ? '- Top up session keys (e.g., "Add $50 to session key", "Top up with $100")' : ""}
4662
- ${enabledFeatures.includes("revokeSession") ? '- Revoke session keys (e.g., "Revoke session key", "Cancel my session")' : ""}
4663
- ${enabledFeatures.includes("enableAutonomy") ? '- Enable autonomous mode (e.g., "Enable auto-pay with $25 limit")' : ""}
4664
-
4665
- Response Format:
4666
- Always respond with valid JSON:
4667
- {
4668
- "action": "payment" | "create_session" | "check_balance" | "check_status" | "topup" | "revoke" | "enable_autonomy" | "chat_only",
4669
- "amount_usd": <number if applicable>,
4670
- "description": "<description>",
4671
- "message": "<friendly response to user>"
4672
- }
4673
-
4674
- Examples:
4675
- User: "Buy coffee for $5"
4676
- You: {"action": "payment", "amount_usd": 5, "description": "coffee", "message": "Sure! Processing your $5 coffee payment..."}
4677
-
4678
- User: "Create a session key with $100"
4679
- You: {"action": "create_session", "amount_usd": 100, "message": "I'll create a session key with a $100 budget..."}
4680
-
4681
- User: "What's my balance?"
4682
- You: {"action": "check_balance", "message": "Let me check your balance..."}
4683
-
4684
- Be helpful, concise, and always respond in valid JSON format.`;
4685
- }
4686
- // ============================================
4687
- // Keyword Detection
4688
- // ============================================
4689
- static containsPaymentKeywords(text) {
4690
- const keywords = [
4691
- "buy",
4692
- "purchase",
4693
- "pay",
4694
- "send",
4695
- "transfer",
4696
- "payment",
4697
- "order",
4698
- "checkout",
4699
- "subscribe",
4700
- "donate",
4701
- "tip"
4702
- ];
4703
- return keywords.some((kw) => text.includes(kw));
4704
- }
4705
- static containsSessionKeywords(text) {
4706
- const keywords = [
4707
- "create session",
4708
- "new session",
4709
- "session key",
4710
- "setup session",
4711
- "generate session",
4712
- "make session",
4713
- "start session"
4714
- ];
4715
- return keywords.some((kw) => text.includes(kw));
4716
- }
4717
- static containsStatusKeywords(text) {
4718
- const keywords = [
4719
- "status",
4720
- "balance",
4721
- "remaining",
4722
- "left",
4723
- "how much",
4724
- "check",
4725
- "show",
4726
- "view",
4727
- "display"
4728
- ];
4729
- return keywords.some((kw) => text.includes(kw));
4730
- }
4731
- static containsTopUpKeywords(text) {
4732
- const keywords = [
4733
- "top up",
4734
- "topup",
4735
- "add funds",
4736
- "add money",
4737
- "fund",
4738
- "increase",
4739
- "reload",
4740
- "refill"
4741
- ];
4742
- return keywords.some((kw) => text.includes(kw));
4743
- }
4744
- static containsRevokeKeywords(text) {
4745
- const keywords = [
4746
- "revoke",
4747
- "cancel",
4748
- "disable",
4749
- "remove",
4750
- "delete",
4751
- "stop",
4752
- "end",
4753
- "terminate"
4754
- ];
4755
- return keywords.some((kw) => text.includes(kw));
4756
- }
4757
- static containsAutonomyKeywords(text) {
4758
- const keywords = [
4759
- "autonomous",
4760
- "auto",
4761
- "automatic",
4762
- "delegate",
4763
- "enable auto",
4764
- "auto-sign",
4765
- "auto sign",
4766
- "auto-pay",
4767
- "auto pay"
4768
- ];
4769
- return keywords.some((kw) => text.includes(kw));
4770
- }
4771
- // ============================================
4772
- // Amount Extraction
4773
- // ============================================
4774
- static extractAmount(text) {
4775
- const dollarMatch = text.match(/\$\s*(\d+(?:\.\d{1,2})?)/);
4776
- if (dollarMatch) {
4777
- return parseFloat(dollarMatch[1]);
4778
- }
4779
- const usdMatch = text.match(/(\d+(?:\.\d{1,2})?)\s*(?:usd|usdc|dollars?)/i);
4780
- if (usdMatch) {
4781
- return parseFloat(usdMatch[1]);
4782
- }
4783
- const numberMatch = text.match(/(\d+(?:\.\d{1,2})?)/);
4784
- if (numberMatch) {
4785
- const num = parseFloat(numberMatch[1]);
4786
- if (num > 0 && num < 1e5) {
4787
- return num;
4788
- }
4789
- }
4790
- return void 0;
4791
- }
4792
- // ============================================
4793
- // Description Extraction
4794
- // ============================================
4795
- static extractDescription(text) {
4796
- const lowerText = text.toLowerCase();
4797
- const items = {
4798
- "coffee": ["coffee", "espresso", "latte", "cappuccino"],
4799
- "food": ["food", "meal", "lunch", "dinner", "breakfast"],
4800
- "drink": ["drink", "beverage", "soda", "juice"],
4801
- "book": ["book", "ebook", "magazine"],
4802
- "subscription": ["subscription", "membership", "plan"],
4803
- "tip": ["tip", "gratuity"],
4804
- "donation": ["donate", "donation", "contribute"],
4805
- "game": ["game", "gaming"],
4806
- "music": ["music", "song", "album"],
4807
- "video": ["video", "movie", "film"]
4808
- };
4809
- for (const [item, keywords] of Object.entries(items)) {
4810
- if (keywords.some((kw) => lowerText.includes(kw))) {
4811
- return item;
4812
- }
4813
- }
4814
- const forMatch = text.match(/for\s+(.+?)(?:\s+\$|\s+usd|$)/i);
4815
- if (forMatch && forMatch[1]) {
4816
- const desc = forMatch[1].trim();
4817
- if (desc.length > 0 && desc.length < 50) {
4818
- return desc;
4819
- }
4820
- }
4821
- return void 0;
4822
- }
4823
- };
4824
- var OpenAIAdapter = class {
4825
- constructor(apiKey, model = "gpt-4o-mini", capabilities) {
4826
- this.apiKey = apiKey;
4827
- this.model = model;
4828
- this.capabilities = capabilities;
4829
- }
4830
- async chat(message, conversationHistory = []) {
4831
- const systemPrompt = PaymentIntentParser.generateSystemPrompt(this.capabilities);
4832
- const response = await fetch("https://api.openai.com/v1/chat/completions", {
4833
- method: "POST",
4834
- headers: {
4835
- "Authorization": `Bearer ${this.apiKey}`,
4836
- "Content-Type": "application/json"
4837
- },
4838
- body: JSON.stringify({
4839
- model: this.model,
4840
- messages: [
4841
- { role: "system", content: systemPrompt },
4842
- ...conversationHistory,
4843
- { role: "user", content: message }
4844
- ],
4845
- temperature: 0.7,
4846
- max_tokens: 500
4847
- })
4848
- });
4849
- if (!response.ok) {
4850
- throw new Error(`OpenAI API error: ${response.status} ${response.statusText}`);
4851
- }
4852
- const data = await response.json();
4853
- const text = data.choices[0]?.message?.content || "";
4854
- let intent = null;
4855
- try {
4856
- const jsonMatch = text.match(/\{[\s\S]*\}/);
4857
- if (jsonMatch) {
4858
- const parsed = JSON.parse(jsonMatch[0]);
4859
- intent = {
4860
- action: parsed.action || "chat_only",
4861
- amount: parsed.amount_usd,
4862
- description: parsed.description,
4863
- confidence: 0.9,
4864
- rawText: text,
4865
- metadata: { message: parsed.message }
4866
- };
4867
- }
4868
- } catch {
4869
- intent = PaymentIntentParser.parse(text);
4870
- }
4871
- return { text, intent, raw: data };
4872
- }
4873
- };
4874
- var AnthropicAdapter = class {
4875
- constructor(apiKey, model = "claude-3-5-sonnet-20241022", capabilities) {
4876
- this.apiKey = apiKey;
4877
- this.model = model;
4878
- this.capabilities = capabilities;
4879
- }
4880
- async chat(message, conversationHistory = []) {
4881
- const systemPrompt = PaymentIntentParser.generateSystemPrompt(this.capabilities);
4882
- const response = await fetch("https://api.anthropic.com/v1/messages", {
4883
- method: "POST",
4884
- headers: {
4885
- "x-api-key": this.apiKey,
4886
- "anthropic-version": "2023-06-01",
4887
- "Content-Type": "application/json"
4888
- },
4889
- body: JSON.stringify({
4890
- model: this.model,
4891
- system: systemPrompt,
4892
- messages: [
4893
- ...conversationHistory.map((msg) => ({
4894
- role: msg.role === "user" ? "user" : "assistant",
4895
- content: msg.content
4896
- })),
4897
- { role: "user", content: message }
4898
- ],
4899
- max_tokens: 500
4900
- })
4901
- });
4902
- if (!response.ok) {
4903
- throw new Error(`Anthropic API error: ${response.status} ${response.statusText}`);
4904
- }
4905
- const data = await response.json();
4906
- const text = data.content[0]?.text || "";
4907
- let intent = null;
4908
- try {
4909
- const jsonMatch = text.match(/\{[\s\S]*\}/);
4910
- if (jsonMatch) {
4911
- const parsed = JSON.parse(jsonMatch[0]);
4912
- intent = {
4913
- action: parsed.action || "chat_only",
4914
- amount: parsed.amount_usd,
4915
- description: parsed.description,
4916
- confidence: 0.9,
4917
- rawText: text,
4918
- metadata: { message: parsed.message }
4919
- };
4920
- }
4921
- } catch {
4922
- intent = PaymentIntentParser.parse(text);
4923
- }
4924
- return { text, intent, raw: data };
4925
- }
4926
- };
4927
- var GeminiAdapter = class {
4928
- constructor(apiKey, model = "gemini-2.0-flash-exp", capabilities) {
4929
- this.apiKey = apiKey;
4930
- this.model = model;
4931
- this.capabilities = capabilities;
4932
- }
4933
- async chat(message) {
4934
- const systemPrompt = PaymentIntentParser.generateSystemPrompt(this.capabilities);
4935
- const fullPrompt = `${systemPrompt}
4936
-
4937
- User: ${message}`;
4938
- const response = await fetch(
4939
- `https://generativelanguage.googleapis.com/v1beta/models/${this.model}:generateContent?key=${this.apiKey}`,
4940
- {
4941
- method: "POST",
4942
- headers: { "Content-Type": "application/json" },
4943
- body: JSON.stringify({
4944
- contents: [{ parts: [{ text: fullPrompt }] }]
4945
- })
4946
- }
4947
- );
4948
- if (!response.ok) {
4949
- throw new Error(`Gemini API error: ${response.status} ${response.statusText}`);
4950
- }
4951
- const data = await response.json();
4952
- const text = data.candidates?.[0]?.content?.parts?.[0]?.text || "";
4953
- let intent = null;
4954
- try {
4955
- const jsonMatch = text.match(/\{[\s\S]*\}/);
4956
- if (jsonMatch) {
4957
- const parsed = JSON.parse(jsonMatch[0]);
4958
- intent = {
4959
- action: parsed.action || "chat_only",
4960
- amount: parsed.amount_usd,
4961
- description: parsed.description,
4962
- confidence: 0.9,
4963
- rawText: text,
4964
- metadata: { message: parsed.message }
4965
- };
4966
- }
4967
- } catch {
4968
- intent = PaymentIntentParser.parse(text);
4969
- }
4970
- return { text, intent, raw: data };
2110
+ return await processPayload(payload, handlers, fullConfig);
2111
+ } catch (err) {
2112
+ return {
2113
+ success: false,
2114
+ processed: false,
2115
+ error: err.message,
2116
+ statusCode: 500
2117
+ };
4971
2118
  }
4972
- };
2119
+ }
4973
2120
 
4974
2121
  // src/helpers/wallet.ts
4975
2122
  var WalletConnector = class _WalletConnector {
@@ -5263,323 +2410,6 @@ function createWalletHook() {
5263
2410
  };
5264
2411
  }
5265
2412
 
5266
- // src/helpers/security.ts
5267
- var PINValidator = class {
5268
- /**
5269
- * Validate PIN strength and format
5270
- */
5271
- static validate(pin) {
5272
- const errors = [];
5273
- const suggestions = [];
5274
- if (!pin || pin.length < 4) {
5275
- errors.push("PIN must be at least 4 digits");
5276
- }
5277
- if (pin.length > 12) {
5278
- errors.push("PIN must be at most 12 digits");
5279
- }
5280
- if (!/^\d+$/.test(pin)) {
5281
- errors.push("PIN must contain only digits");
5282
- }
5283
- if (this.isWeakPattern(pin)) {
5284
- errors.push("PIN is too simple");
5285
- suggestions.push("Avoid sequential numbers (1234) or repeated digits (1111)");
5286
- }
5287
- let strength = "strong";
5288
- if (errors.length > 0) {
5289
- strength = "weak";
5290
- } else if (pin.length <= 4 || this.hasRepeatedDigits(pin)) {
5291
- strength = "weak";
5292
- suggestions.push("Use at least 6 digits for better security");
5293
- } else if (pin.length <= 6) {
5294
- strength = "medium";
5295
- suggestions.push("Use 8+ digits for maximum security");
5296
- }
5297
- return {
5298
- valid: errors.length === 0,
5299
- strength,
5300
- errors,
5301
- suggestions
5302
- };
5303
- }
5304
- /**
5305
- * Check PIN strength (0-100 score)
5306
- */
5307
- static strengthScore(pin) {
5308
- if (!pin) return 0;
5309
- let score = 0;
5310
- score += Math.min(pin.length * 10, 50);
5311
- const uniqueDigits = new Set(pin).size;
5312
- score += uniqueDigits * 5;
5313
- if (!this.isSequential(pin)) {
5314
- score += 20;
5315
- }
5316
- if (!this.hasRepeatedDigits(pin)) {
5317
- score += 10;
5318
- }
5319
- return Math.min(score, 100);
5320
- }
5321
- /**
5322
- * Generate a secure random PIN
5323
- */
5324
- static generateSecureRandom(length = 6) {
5325
- if (length < 4 || length > 12) {
5326
- throw new Error("PIN length must be between 4 and 12");
5327
- }
5328
- const array = new Uint32Array(length);
5329
- crypto.getRandomValues(array);
5330
- const pin = Array.from(array).map((num) => num % 10).join("");
5331
- if (this.isWeakPattern(pin)) {
5332
- return this.generateSecureRandom(length);
5333
- }
5334
- return pin;
5335
- }
5336
- /**
5337
- * Check for weak patterns
5338
- */
5339
- static isWeakPattern(pin) {
5340
- if (this.isSequential(pin)) return true;
5341
- if (/^(\d)\1+$/.test(pin)) return true;
5342
- const commonPatterns = [
5343
- "1234",
5344
- "4321",
5345
- "1111",
5346
- "2222",
5347
- "3333",
5348
- "4444",
5349
- "5555",
5350
- "6666",
5351
- "7777",
5352
- "8888",
5353
- "9999",
5354
- "0000",
5355
- "1212",
5356
- "2323",
5357
- "0123",
5358
- "3210",
5359
- "9876",
5360
- "6789"
5361
- ];
5362
- return commonPatterns.some((pattern) => pin.includes(pattern));
5363
- }
5364
- /**
5365
- * Check if PIN is sequential
5366
- */
5367
- static isSequential(pin) {
5368
- for (let i = 1; i < pin.length; i++) {
5369
- const diff = parseInt(pin[i]) - parseInt(pin[i - 1]);
5370
- if (Math.abs(diff) !== 1) return false;
5371
- }
5372
- return true;
5373
- }
5374
- /**
5375
- * Check if PIN has repeated digits
5376
- */
5377
- static hasRepeatedDigits(pin) {
5378
- return /(\d)\1{2,}/.test(pin);
5379
- }
5380
- /**
5381
- * Hash PIN for storage (if needed)
5382
- * Note: For device-bound keys, the PIN is used for key derivation, not storage
5383
- */
5384
- static async hashPIN(pin, salt) {
5385
- const actualSalt = salt || crypto.getRandomValues(new Uint8Array(16));
5386
- const encoder = new TextEncoder();
5387
- const data = encoder.encode(pin + actualSalt);
5388
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
5389
- const hashArray = Array.from(new Uint8Array(hashBuffer));
5390
- return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
5391
- }
5392
- };
5393
- var PINRateLimiter = class {
5394
- attempts = /* @__PURE__ */ new Map();
5395
- locked = /* @__PURE__ */ new Map();
5396
- maxAttempts;
5397
- windowMs;
5398
- lockoutMs;
5399
- constructor(config = {}) {
5400
- this.maxAttempts = config.maxAttempts || 3;
5401
- this.windowMs = config.windowMs || 6e4;
5402
- this.lockoutMs = config.lockoutMs || 3e5;
5403
- }
5404
- /**
5405
- * Check if attempt is allowed
5406
- */
5407
- async checkAttempt(sessionKeyId) {
5408
- const now = Date.now();
5409
- const lockoutUntil = this.locked.get(sessionKeyId);
5410
- if (lockoutUntil && now < lockoutUntil) {
5411
- const lockoutSeconds = Math.ceil((lockoutUntil - now) / 1e3);
5412
- return {
5413
- allowed: false,
5414
- remainingAttempts: 0,
5415
- lockoutSeconds
5416
- };
5417
- }
5418
- if (lockoutUntil && now >= lockoutUntil) {
5419
- this.locked.delete(sessionKeyId);
5420
- this.attempts.delete(sessionKeyId);
5421
- }
5422
- const recentAttempts = this.getRecentAttempts(sessionKeyId, now);
5423
- const remainingAttempts = this.maxAttempts - recentAttempts.length;
5424
- if (remainingAttempts <= 0) {
5425
- this.locked.set(sessionKeyId, now + this.lockoutMs);
5426
- return {
5427
- allowed: false,
5428
- remainingAttempts: 0,
5429
- lockoutSeconds: Math.ceil(this.lockoutMs / 1e3)
5430
- };
5431
- }
5432
- return {
5433
- allowed: true,
5434
- remainingAttempts
5435
- };
5436
- }
5437
- /**
5438
- * Record a failed attempt
5439
- */
5440
- recordFailedAttempt(sessionKeyId) {
5441
- const now = Date.now();
5442
- const attempts = this.attempts.get(sessionKeyId) || [];
5443
- attempts.push(now);
5444
- this.attempts.set(sessionKeyId, attempts);
5445
- }
5446
- /**
5447
- * Record a successful attempt (clears history)
5448
- */
5449
- recordSuccessfulAttempt(sessionKeyId) {
5450
- this.attempts.delete(sessionKeyId);
5451
- this.locked.delete(sessionKeyId);
5452
- }
5453
- /**
5454
- * Get recent attempts within window
5455
- */
5456
- getRecentAttempts(sessionKeyId, now) {
5457
- const attempts = this.attempts.get(sessionKeyId) || [];
5458
- const cutoff = now - this.windowMs;
5459
- const recent = attempts.filter((timestamp) => timestamp > cutoff);
5460
- if (recent.length !== attempts.length) {
5461
- this.attempts.set(sessionKeyId, recent);
5462
- }
5463
- return recent;
5464
- }
5465
- /**
5466
- * Clear all rate limit data
5467
- */
5468
- clear() {
5469
- this.attempts.clear();
5470
- this.locked.clear();
5471
- }
5472
- /**
5473
- * Check lockout status
5474
- */
5475
- isLockedOut(sessionKeyId) {
5476
- const lockoutUntil = this.locked.get(sessionKeyId);
5477
- return lockoutUntil ? Date.now() < lockoutUntil : false;
5478
- }
5479
- /**
5480
- * Get remaining lockout time
5481
- */
5482
- getRemainingLockoutTime(sessionKeyId) {
5483
- const lockoutUntil = this.locked.get(sessionKeyId);
5484
- if (!lockoutUntil) return 0;
5485
- return Math.max(0, lockoutUntil - Date.now());
5486
- }
5487
- };
5488
- var SecureStorage = class {
5489
- /**
5490
- * Store data with encryption (basic)
5491
- * For production, consider using Web Crypto API with user-derived keys
5492
- */
5493
- static async setEncrypted(key, value, secret) {
5494
- const encrypted = await this.encrypt(value, secret);
5495
- localStorage.setItem(key, JSON.stringify(encrypted));
5496
- }
5497
- /**
5498
- * Retrieve encrypted data
5499
- */
5500
- static async getEncrypted(key, secret) {
5501
- const stored = localStorage.getItem(key);
5502
- if (!stored) return null;
5503
- try {
5504
- const encrypted = JSON.parse(stored);
5505
- return await this.decrypt(encrypted, secret);
5506
- } catch {
5507
- return null;
5508
- }
5509
- }
5510
- /**
5511
- * Encrypt string with AES-GCM
5512
- */
5513
- static async encrypt(plaintext, secret) {
5514
- const encoder = new TextEncoder();
5515
- const data = encoder.encode(plaintext);
5516
- const key = await this.deriveKey(secret);
5517
- const iv = crypto.getRandomValues(new Uint8Array(12));
5518
- const encrypted = await crypto.subtle.encrypt(
5519
- { name: "AES-GCM", iv },
5520
- key,
5521
- data
5522
- );
5523
- return {
5524
- ciphertext: btoa(String.fromCharCode(...new Uint8Array(encrypted))),
5525
- iv: btoa(String.fromCharCode(...iv))
5526
- };
5527
- }
5528
- /**
5529
- * Decrypt AES-GCM ciphertext
5530
- */
5531
- static async decrypt(encrypted, secret) {
5532
- const key = await this.deriveKey(secret);
5533
- const iv = Uint8Array.from(atob(encrypted.iv), (c) => c.charCodeAt(0));
5534
- const ciphertext = Uint8Array.from(atob(encrypted.ciphertext), (c) => c.charCodeAt(0));
5535
- const decrypted = await crypto.subtle.decrypt(
5536
- { name: "AES-GCM", iv },
5537
- key,
5538
- ciphertext
5539
- );
5540
- const decoder = new TextDecoder();
5541
- return decoder.decode(decrypted);
5542
- }
5543
- /**
5544
- * Derive encryption key from secret
5545
- */
5546
- static async deriveKey(secret) {
5547
- const encoder = new TextEncoder();
5548
- const keyMaterial = await crypto.subtle.importKey(
5549
- "raw",
5550
- encoder.encode(secret),
5551
- { name: "PBKDF2" },
5552
- false,
5553
- ["deriveBits", "deriveKey"]
5554
- );
5555
- return await crypto.subtle.deriveKey(
5556
- {
5557
- name: "PBKDF2",
5558
- salt: encoder.encode("zendfi-secure-storage"),
5559
- iterations: 1e5,
5560
- hash: "SHA-256"
5561
- },
5562
- keyMaterial,
5563
- { name: "AES-GCM", length: 256 },
5564
- false,
5565
- ["encrypt", "decrypt"]
5566
- );
5567
- }
5568
- /**
5569
- * Clear all secure storage
5570
- */
5571
- static clearAll(namespace = "zendfi") {
5572
- const keysToRemove = [];
5573
- for (let i = 0; i < localStorage.length; i++) {
5574
- const key = localStorage.key(i);
5575
- if (key?.startsWith(namespace)) {
5576
- keysToRemove.push(key);
5577
- }
5578
- }
5579
- keysToRemove.forEach((key) => localStorage.removeItem(key));
5580
- }
5581
- };
5582
-
5583
2413
  // src/helpers/polling.ts
5584
2414
  var TransactionPoller = class {
5585
2415
  /**
@@ -5840,403 +2670,6 @@ var TransactionMonitor = class {
5840
2670
  }
5841
2671
  };
5842
2672
 
5843
- // src/helpers/recovery.ts
5844
- var RetryStrategy = class {
5845
- /**
5846
- * Execute function with retry logic
5847
- */
5848
- static async withRetry(fn, options = {}) {
5849
- const {
5850
- maxAttempts = 3,
5851
- backoffMs = 1e3,
5852
- backoffMultiplier = 2,
5853
- maxBackoffMs = 3e4,
5854
- shouldRetry = () => true,
5855
- onRetry
5856
- } = options;
5857
- let lastError = null;
5858
- let currentBackoff = backoffMs;
5859
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
5860
- try {
5861
- return await fn();
5862
- } catch (error) {
5863
- lastError = error;
5864
- if (attempt === maxAttempts || !shouldRetry(error, attempt)) {
5865
- throw error;
5866
- }
5867
- const jitter = Math.random() * 0.3 * currentBackoff;
5868
- const nextDelay = Math.min(currentBackoff + jitter, maxBackoffMs);
5869
- onRetry?.(error, attempt, nextDelay);
5870
- await this.sleep(nextDelay);
5871
- currentBackoff *= backoffMultiplier;
5872
- }
5873
- }
5874
- throw lastError || new Error("Retry failed");
5875
- }
5876
- /**
5877
- * Retry with linear backoff
5878
- */
5879
- static async withLinearRetry(fn, maxAttempts = 3, delayMs = 1e3) {
5880
- return this.withRetry(fn, {
5881
- maxAttempts,
5882
- backoffMs: delayMs,
5883
- backoffMultiplier: 1
5884
- // Linear
5885
- });
5886
- }
5887
- /**
5888
- * Retry with custom backoff function
5889
- */
5890
- static async withCustomBackoff(fn, backoffFn, maxAttempts = 3) {
5891
- let lastError = null;
5892
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
5893
- try {
5894
- return await fn();
5895
- } catch (error) {
5896
- lastError = error;
5897
- if (attempt === maxAttempts) {
5898
- throw error;
5899
- }
5900
- const delay = backoffFn(attempt);
5901
- await this.sleep(delay);
5902
- }
5903
- }
5904
- throw lastError || new Error("Retry failed");
5905
- }
5906
- /**
5907
- * Check if error is retryable
5908
- */
5909
- static isRetryableError(error) {
5910
- if (error.name === "NetworkError" || error.message?.includes("network")) {
5911
- return true;
5912
- }
5913
- if (error.name === "TimeoutError" || error.message?.includes("timeout")) {
5914
- return true;
5915
- }
5916
- if (error.status === 429 || error.code === "RATE_LIMIT_EXCEEDED") {
5917
- return true;
5918
- }
5919
- if (error.status >= 500 && error.status < 600) {
5920
- return true;
5921
- }
5922
- if (error.message?.includes("blockhash") || error.message?.includes("recent")) {
5923
- return true;
5924
- }
5925
- return false;
5926
- }
5927
- static sleep(ms) {
5928
- return new Promise((resolve) => setTimeout(resolve, ms));
5929
- }
5930
- };
5931
- var ErrorRecovery = class {
5932
- /**
5933
- * Recover from network errors with retry
5934
- */
5935
- static async recoverFromNetworkError(fn, maxAttempts = 3) {
5936
- return RetryStrategy.withRetry(fn, {
5937
- maxAttempts,
5938
- backoffMs: 2e3,
5939
- shouldRetry: (error) => {
5940
- return error.name === "NetworkError" || error.message?.includes("network") || error.message?.includes("fetch");
5941
- },
5942
- onRetry: (error, attempt, nextDelay) => {
5943
- console.warn(
5944
- `Network error (attempt ${attempt}), retrying in ${nextDelay}ms:`,
5945
- error.message
5946
- );
5947
- }
5948
- });
5949
- }
5950
- /**
5951
- * Recover from rate limit errors with exponential backoff
5952
- */
5953
- static async recoverFromRateLimit(fn, maxAttempts = 5) {
5954
- return RetryStrategy.withRetry(fn, {
5955
- maxAttempts,
5956
- backoffMs: 5e3,
5957
- // Start with 5 seconds
5958
- backoffMultiplier: 2,
5959
- maxBackoffMs: 6e4,
5960
- // Cap at 1 minute
5961
- shouldRetry: (error) => {
5962
- return error.status === 429 || error.code === "RATE_LIMIT_EXCEEDED";
5963
- },
5964
- onRetry: (attempt, nextDelay) => {
5965
- console.warn(
5966
- `Rate limited (attempt ${attempt}), waiting ${nextDelay}ms before retry`
5967
- );
5968
- }
5969
- });
5970
- }
5971
- /**
5972
- * Recover from Solana RPC errors (blockhash, etc.)
5973
- */
5974
- static async recoverFromRPCError(fn, maxAttempts = 3) {
5975
- return RetryStrategy.withRetry(fn, {
5976
- maxAttempts,
5977
- backoffMs: 1e3,
5978
- shouldRetry: (error) => {
5979
- const message = error.message?.toLowerCase() || "";
5980
- return message.includes("blockhash") || message.includes("recent") || message.includes("slot") || message.includes("rpc");
5981
- },
5982
- onRetry: (error, attempt, nextDelay) => {
5983
- console.warn(
5984
- `RPC error (attempt ${attempt}), retrying in ${nextDelay}ms:`,
5985
- error.message
5986
- );
5987
- }
5988
- });
5989
- }
5990
- /**
5991
- * Recover from timeout errors
5992
- */
5993
- static async recoverFromTimeout(fn, timeoutMs = 3e4, maxAttempts = 2) {
5994
- return RetryStrategy.withRetry(
5995
- () => this.withTimeout(fn, timeoutMs),
5996
- {
5997
- maxAttempts,
5998
- backoffMs: 5e3,
5999
- shouldRetry: (error) => {
6000
- return error.name === "TimeoutError" || error.message?.includes("timeout");
6001
- }
6002
- }
6003
- );
6004
- }
6005
- /**
6006
- * Add timeout to async function
6007
- */
6008
- static async withTimeout(fn, timeoutMs) {
6009
- return Promise.race([
6010
- fn(),
6011
- new Promise((_, reject) => {
6012
- setTimeout(() => {
6013
- reject(new Error(`Operation timed out after ${timeoutMs}ms`));
6014
- }, timeoutMs);
6015
- })
6016
- ]);
6017
- }
6018
- /**
6019
- * Circuit breaker pattern for repeated failures
6020
- */
6021
- static createCircuitBreaker(fn, options = {}) {
6022
- const {
6023
- failureThreshold = 5,
6024
- resetTimeoutMs = 6e4,
6025
- onStateChange
6026
- } = options;
6027
- let state = "closed";
6028
- let failureCount = 0;
6029
- let lastFailureTime = 0;
6030
- let resetTimer = null;
6031
- return async () => {
6032
- if (state === "open" && Date.now() - lastFailureTime > resetTimeoutMs) {
6033
- state = "half-open";
6034
- onStateChange?.("half-open");
6035
- }
6036
- if (state === "open") {
6037
- throw new Error("Circuit breaker is OPEN - too many failures");
6038
- }
6039
- try {
6040
- const result = await fn();
6041
- if (state === "half-open") {
6042
- state = "closed";
6043
- onStateChange?.("closed");
6044
- }
6045
- failureCount = 0;
6046
- return result;
6047
- } catch (error) {
6048
- failureCount++;
6049
- lastFailureTime = Date.now();
6050
- if (failureCount >= failureThreshold) {
6051
- state = "open";
6052
- onStateChange?.("open");
6053
- if (resetTimer) clearTimeout(resetTimer);
6054
- resetTimer = setTimeout(() => {
6055
- state = "half-open";
6056
- onStateChange?.("half-open");
6057
- }, resetTimeoutMs);
6058
- }
6059
- throw error;
6060
- }
6061
- };
6062
- }
6063
- /**
6064
- * Fallback to alternative function on error
6065
- */
6066
- static async withFallback(primaryFn, fallbackFn, shouldFallback = () => true) {
6067
- try {
6068
- return await primaryFn();
6069
- } catch (error) {
6070
- if (shouldFallback(error)) {
6071
- console.warn("Primary function failed, using fallback:", error.message);
6072
- return await fallbackFn();
6073
- }
6074
- throw error;
6075
- }
6076
- }
6077
- /**
6078
- * Graceful degradation - return partial result on error
6079
- */
6080
- static async withGracefulDegradation(fn, defaultValue, onError) {
6081
- try {
6082
- return await fn();
6083
- } catch (error) {
6084
- onError?.(error);
6085
- console.warn("Operation failed, returning default value:", error.message);
6086
- return defaultValue;
6087
- }
6088
- }
6089
- };
6090
-
6091
- // src/helpers/lifecycle.ts
6092
- var SessionKeyLifecycle = class {
6093
- constructor(client, config = {}) {
6094
- this.client = client;
6095
- this.config = config;
6096
- if (config.autoCleanup && typeof window !== "undefined") {
6097
- window.addEventListener("beforeunload", () => {
6098
- this.cleanup().catch(console.error);
6099
- });
6100
- }
6101
- }
6102
- sessionKeyId = null;
6103
- sessionWallet = null;
6104
- /**
6105
- * Create and fund session key in one call
6106
- * Handles: keypair generation → encryption → backend registration
6107
- * Note: The SDK now handles all crypto internally
6108
- */
6109
- async createAndFund(config) {
6110
- const pin = this.config.pinProvider ? await this.config.pinProvider() : await this.promptForPIN("Create PIN for session key");
6111
- const response = await this.client.sessionKeys.create({
6112
- userWallet: config.userWallet,
6113
- agentId: config.agentId,
6114
- agentName: config.agentName,
6115
- limitUSDC: config.limitUsdc,
6116
- durationDays: config.durationDays || 7,
6117
- pin,
6118
- // SDK handles encryption internally
6119
- generateRecoveryQR: false
6120
- });
6121
- this.sessionKeyId = response.sessionKeyId;
6122
- this.sessionWallet = response.sessionWallet;
6123
- if (config.onApprovalNeeded) {
6124
- await config.onApprovalNeeded(`Fund session wallet: ${this.sessionWallet}`);
6125
- }
6126
- return {
6127
- sessionKeyId: this.sessionKeyId,
6128
- sessionWallet: this.sessionWallet
6129
- };
6130
- }
6131
- /**
6132
- * Make a payment using the session key
6133
- * Uses the new SDK's makePayment which handles caching internally
6134
- */
6135
- async pay(amount, description) {
6136
- if (!this.sessionKeyId || !this.sessionWallet) {
6137
- throw new Error("No active session key. Call createAndFund() first.");
6138
- }
6139
- const pin = this.config.pinProvider ? await this.config.pinProvider() : void 0;
6140
- const result = await this.client.sessionKeys.makePayment({
6141
- sessionKeyId: this.sessionKeyId,
6142
- amount,
6143
- recipient: this.sessionWallet,
6144
- description,
6145
- pin
6146
- // SDK will only use PIN if needed
6147
- });
6148
- return {
6149
- paymentId: result.paymentId,
6150
- status: result.status,
6151
- signature: result.signature
6152
- };
6153
- }
6154
- /**
6155
- * Check session key status
6156
- */
6157
- async getStatus() {
6158
- if (!this.sessionKeyId) {
6159
- throw new Error("No active session key");
6160
- }
6161
- return await this.client.sessionKeys.getStatus(this.sessionKeyId);
6162
- }
6163
- /**
6164
- * Top up session key
6165
- * @deprecated Device-bound session keys are funded directly by the user.
6166
- * Use the session wallet address to send funds directly.
6167
- */
6168
- async topUp(_amount, _userWallet, _onApprovalNeeded) {
6169
- if (!this.sessionWallet) {
6170
- throw new Error("No active session key");
6171
- }
6172
- console.warn(
6173
- `topUp() is deprecated for device-bound session keys. Send funds directly to the session wallet: ${this.sessionWallet}`
6174
- );
6175
- }
6176
- /**
6177
- * Revoke session key
6178
- */
6179
- async revoke() {
6180
- if (!this.sessionKeyId) {
6181
- throw new Error("No active session key");
6182
- }
6183
- await this.client.sessionKeys.revoke(this.sessionKeyId);
6184
- await this.cleanup();
6185
- }
6186
- /**
6187
- * Cleanup (clear cache, reset state)
6188
- */
6189
- async cleanup() {
6190
- if (this.config.cache && this.sessionKeyId) {
6191
- await this.config.cache.invalidate(this.sessionKeyId);
6192
- }
6193
- if (this.sessionKeyId) {
6194
- this.client.sessionKeys.clearCache(this.sessionKeyId);
6195
- }
6196
- this.sessionKeyId = null;
6197
- this.sessionWallet = null;
6198
- }
6199
- /**
6200
- * Get current session key ID
6201
- */
6202
- getSessionKeyId() {
6203
- return this.sessionKeyId;
6204
- }
6205
- /**
6206
- * Check if session is active
6207
- */
6208
- isActive() {
6209
- return this.sessionKeyId !== null;
6210
- }
6211
- // ============================================
6212
- // Private Helpers
6213
- // ============================================
6214
- async promptForPIN(message) {
6215
- if (typeof window !== "undefined" && window.prompt) {
6216
- const pin = window.prompt(message);
6217
- if (!pin) {
6218
- throw new Error("PIN required");
6219
- }
6220
- return pin;
6221
- }
6222
- throw new Error("PIN provider not configured and no browser prompt available");
6223
- }
6224
- };
6225
- async function setupQuickSessionKey(client, config) {
6226
- const { SessionKeyCache: SessionKeyCache2 } = await Promise.resolve().then(() => (init_cache(), cache_exports));
6227
- const lifecycle = new SessionKeyLifecycle(client, {
6228
- cache: new SessionKeyCache2({ storage: "localStorage", ttl: 36e5 }),
6229
- autoCleanup: true
6230
- });
6231
- await lifecycle.createAndFund({
6232
- userWallet: config.userWallet,
6233
- agentId: config.agentId,
6234
- limitUsdc: config.budgetUsdc,
6235
- onApprovalNeeded: config.onApproval
6236
- });
6237
- return lifecycle;
6238
- }
6239
-
6240
2673
  // src/helpers/dev.ts
6241
2674
  var DevTools = class {
6242
2675
  static debugEnabled = false;
@@ -6318,8 +2751,8 @@ var DevTools = class {
6318
2751
  if (!this.isDevelopment()) {
6319
2752
  throw new Error("Test session keys can only be created in development");
6320
2753
  }
6321
- const { Keypair: Keypair2 } = await this.getSolanaWeb3();
6322
- const keypair = Keypair2.generate();
2754
+ const { Keypair } = await this.getSolanaWeb3();
2755
+ const keypair = Keypair.generate();
6323
2756
  return {
6324
2757
  sessionKeyId: this.generateTestId("sk_test"),
6325
2758
  sessionWallet: keypair.publicKey.toString(),
@@ -6581,41 +3014,17 @@ var PerformanceMonitor = class {
6581
3014
  };
6582
3015
  // Annotate the CommonJS export names for ESM import in node:
6583
3016
  0 && (module.exports = {
6584
- AgentAPI,
6585
- AnthropicAdapter,
6586
3017
  ApiError,
6587
3018
  AuthenticationError,
6588
- AutonomyAPI,
6589
3019
  ConfigLoader,
6590
3020
  DevTools,
6591
- DeviceBoundSessionKey,
6592
- DeviceFingerprintGenerator,
6593
3021
  ERROR_CODES,
6594
- ErrorRecovery,
6595
- GeminiAdapter,
6596
3022
  InterceptorManager,
6597
- LitCryptoSigner,
6598
3023
  NetworkError,
6599
- OpenAIAdapter,
6600
- PINRateLimiter,
6601
- PINValidator,
6602
3024
  PaymentError,
6603
- PaymentIntentParser,
6604
- PaymentIntentsAPI,
6605
3025
  PerformanceMonitor,
6606
- PricingAPI,
6607
- QuickCaches,
6608
3026
  RateLimitError,
6609
3027
  RateLimiter,
6610
- RecoveryQRGenerator,
6611
- RetryStrategy,
6612
- SPENDING_LIMIT_ACTION_CID,
6613
- SecureStorage,
6614
- SessionKeyCache,
6615
- SessionKeyCrypto,
6616
- SessionKeyLifecycle,
6617
- SessionKeysAPI,
6618
- SmartPaymentsAPI,
6619
3028
  TransactionMonitor,
6620
3029
  TransactionPoller,
6621
3030
  ValidationError,
@@ -6624,25 +3033,17 @@ var PerformanceMonitor = class {
6624
3033
  ZendFiClient,
6625
3034
  ZendFiEmbeddedCheckout,
6626
3035
  ZendFiError,
6627
- asAgentKeyId,
6628
- asEscrowId,
6629
3036
  asInstallmentPlanId,
6630
- asIntentId,
6631
3037
  asInvoiceId,
6632
3038
  asMerchantId,
6633
3039
  asPaymentId,
6634
3040
  asPaymentLinkCode,
6635
- asSessionId,
6636
3041
  asSubscriptionId,
6637
3042
  createWalletHook,
6638
3043
  createZendFiError,
6639
- decodeSignatureFromLit,
6640
- encodeTransactionForLit,
6641
3044
  generateIdempotencyKey,
6642
3045
  isZendFiError,
6643
3046
  processWebhook,
6644
- requiresLitSigning,
6645
- setupQuickSessionKey,
6646
3047
  sleep,
6647
3048
  verifyExpressWebhook,
6648
3049
  verifyNextWebhook,