@zendfi/sdk 0.5.7 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,6 +5,9 @@ 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
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,31 +30,1015 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
33
+ // src/device-bound-crypto.ts
34
+ var device_bound_crypto_exports = {};
35
+ __export(device_bound_crypto_exports, {
36
+ DeviceBoundSessionKey: () => DeviceBoundSessionKey,
37
+ DeviceFingerprintGenerator: () => DeviceFingerprintGenerator,
38
+ RecoveryQRGenerator: () => RecoveryQRGenerator,
39
+ SessionKeyCrypto: () => SessionKeyCrypto
40
+ });
41
+ var import_web3, crypto2, DeviceFingerprintGenerator, SessionKeyCrypto, RecoveryQRGenerator, DeviceBoundSessionKey;
42
+ var init_device_bound_crypto = __esm({
43
+ "src/device-bound-crypto.ts"() {
44
+ "use strict";
45
+ import_web3 = require("@solana/web3.js");
46
+ crypto2 = __toESM(require("crypto"));
47
+ DeviceFingerprintGenerator = class {
48
+ /**
49
+ * Generate a unique device fingerprint
50
+ * Combines multiple browser attributes for uniqueness
51
+ */
52
+ static async generate() {
53
+ const components = {};
54
+ try {
55
+ components.canvas = await this.getCanvasFingerprint();
56
+ components.webgl = await this.getWebGLFingerprint();
57
+ components.audio = await this.getAudioFingerprint();
58
+ components.screen = `${screen.width}x${screen.height}x${screen.colorDepth}`;
59
+ components.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
60
+ components.languages = navigator.languages?.join(",") || navigator.language;
61
+ components.platform = navigator.platform;
62
+ components.hardwareConcurrency = navigator.hardwareConcurrency?.toString() || "unknown";
63
+ const combined = Object.entries(components).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}:${value}`).join("|");
64
+ const fingerprint = await this.sha256(combined);
65
+ return {
66
+ fingerprint,
67
+ generatedAt: Date.now(),
68
+ components
69
+ };
70
+ } catch (error) {
71
+ console.warn("Device fingerprinting failed, using fallback", error);
72
+ return this.generateFallbackFingerprint();
73
+ }
74
+ }
75
+ /**
76
+ * Graceful fallback fingerprint generation
77
+ * Works in headless browsers, SSR, and restricted environments
78
+ */
79
+ static async generateFallbackFingerprint() {
80
+ const components = {};
81
+ try {
82
+ if (typeof navigator !== "undefined") {
83
+ components.platform = navigator.platform || "unknown";
84
+ components.languages = navigator.languages?.join(",") || navigator.language || "unknown";
85
+ components.hardwareConcurrency = navigator.hardwareConcurrency?.toString() || "unknown";
86
+ }
87
+ if (typeof screen !== "undefined") {
88
+ components.screen = `${screen.width || 0}x${screen.height || 0}x${screen.colorDepth || 0}`;
89
+ }
90
+ if (typeof Intl !== "undefined") {
91
+ try {
92
+ components.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
93
+ } catch {
94
+ components.timezone = "unknown";
95
+ }
96
+ }
97
+ } catch {
98
+ components.platform = "fallback";
99
+ }
100
+ let randomEntropy = "";
101
+ try {
102
+ if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
103
+ const arr = new Uint8Array(16);
104
+ window.crypto.getRandomValues(arr);
105
+ randomEntropy = Array.from(arr).map((b) => b.toString(16).padStart(2, "0")).join("");
106
+ } else if (typeof crypto2 !== "undefined" && crypto2.randomBytes) {
107
+ randomEntropy = crypto2.randomBytes(16).toString("hex");
108
+ }
109
+ } catch {
110
+ randomEntropy = Date.now().toString(36) + Math.random().toString(36);
111
+ }
112
+ const combined = Object.entries(components).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}:${value}`).join("|") + "|entropy:" + randomEntropy;
113
+ const fingerprint = await this.sha256(combined);
114
+ return {
115
+ fingerprint,
116
+ generatedAt: Date.now(),
117
+ components
118
+ };
119
+ }
120
+ static async getCanvasFingerprint() {
121
+ const canvas = document.createElement("canvas");
122
+ const ctx = canvas.getContext("2d");
123
+ if (!ctx) return "no-canvas";
124
+ canvas.width = 200;
125
+ canvas.height = 50;
126
+ ctx.textBaseline = "top";
127
+ ctx.font = '14px "Arial"';
128
+ ctx.fillStyle = "#f60";
129
+ ctx.fillRect(0, 0, 100, 50);
130
+ ctx.fillStyle = "#069";
131
+ ctx.fillText("ZendFi \u{1F510}", 2, 2);
132
+ ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
133
+ ctx.fillText("Device-Bound", 4, 17);
134
+ return canvas.toDataURL();
135
+ }
136
+ static async getWebGLFingerprint() {
137
+ const canvas = document.createElement("canvas");
138
+ const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
139
+ if (!gl) return "no-webgl";
140
+ const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
141
+ if (!debugInfo) return "no-debug-info";
142
+ const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
143
+ const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
144
+ return `${vendor}|${renderer}`;
145
+ }
146
+ static async getAudioFingerprint() {
147
+ try {
148
+ const AudioContext = window.AudioContext || window.webkitAudioContext;
149
+ if (!AudioContext) return "no-audio";
150
+ const context = new AudioContext();
151
+ const oscillator = context.createOscillator();
152
+ const analyser = context.createAnalyser();
153
+ const gainNode = context.createGain();
154
+ const scriptProcessor = context.createScriptProcessor(4096, 1, 1);
155
+ gainNode.gain.value = 0;
156
+ oscillator.connect(analyser);
157
+ analyser.connect(scriptProcessor);
158
+ scriptProcessor.connect(gainNode);
159
+ gainNode.connect(context.destination);
160
+ oscillator.start(0);
161
+ return new Promise((resolve) => {
162
+ scriptProcessor.onaudioprocess = (event) => {
163
+ const output = event.inputBuffer.getChannelData(0);
164
+ const hash = Array.from(output.slice(0, 30)).reduce((acc, val) => acc + Math.abs(val), 0);
165
+ oscillator.stop();
166
+ scriptProcessor.disconnect();
167
+ context.close();
168
+ resolve(hash.toString());
169
+ };
170
+ });
171
+ } catch (error) {
172
+ return "audio-error";
173
+ }
174
+ }
175
+ static async sha256(data) {
176
+ if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
177
+ const encoder = new TextEncoder();
178
+ const dataBuffer = encoder.encode(data);
179
+ const hashBuffer = await window.crypto.subtle.digest("SHA-256", dataBuffer);
180
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
181
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
182
+ } else {
183
+ return crypto2.createHash("sha256").update(data).digest("hex");
184
+ }
185
+ }
186
+ };
187
+ SessionKeyCrypto = class {
188
+ /**
189
+ * Encrypt a Solana keypair with PIN + device fingerprint
190
+ * Uses Argon2id for key derivation and AES-256-GCM for encryption
191
+ */
192
+ static async encrypt(keypair, pin, deviceFingerprint) {
193
+ if (!/^\d{6}$/.test(pin)) {
194
+ throw new Error("PIN must be exactly 6 numeric digits");
195
+ }
196
+ const encryptionKey = await this.deriveKey(pin, deviceFingerprint);
197
+ const nonce = this.generateNonce();
198
+ const secretKey = keypair.secretKey;
199
+ const encryptedData = await this.aesEncrypt(secretKey, encryptionKey, nonce);
200
+ return {
201
+ encryptedData: Buffer.from(encryptedData).toString("base64"),
202
+ nonce: Buffer.from(nonce).toString("base64"),
203
+ publicKey: keypair.publicKey.toBase58(),
204
+ deviceFingerprint,
205
+ version: "argon2id-aes256gcm-v1"
206
+ };
207
+ }
208
+ /**
209
+ * Decrypt an encrypted session key with PIN + device fingerprint
210
+ */
211
+ static async decrypt(encrypted, pin, deviceFingerprint) {
212
+ if (!/^\d{6}$/.test(pin)) {
213
+ throw new Error("PIN must be exactly 6 numeric digits");
214
+ }
215
+ if (encrypted.deviceFingerprint !== deviceFingerprint) {
216
+ throw new Error("Device fingerprint mismatch - wrong device or security threat");
217
+ }
218
+ const encryptionKey = await this.deriveKey(pin, deviceFingerprint);
219
+ const encryptedData = Buffer.from(encrypted.encryptedData, "base64");
220
+ const nonce = Buffer.from(encrypted.nonce, "base64");
221
+ try {
222
+ const secretKey = await this.aesDecrypt(encryptedData, encryptionKey, nonce);
223
+ return import_web3.Keypair.fromSecretKey(secretKey);
224
+ } catch (error) {
225
+ throw new Error("Decryption failed - wrong PIN or corrupted data");
226
+ }
227
+ }
228
+ /**
229
+ * Derive encryption key from PIN + device fingerprint using Argon2id
230
+ *
231
+ * Argon2id parameters (OWASP recommended):
232
+ * - Memory: 64MB (65536 KB)
233
+ * - Iterations: 3
234
+ * - Parallelism: 4
235
+ * - Salt: device fingerprint
236
+ */
237
+ static async deriveKey(pin, deviceFingerprint) {
238
+ if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
239
+ const encoder = new TextEncoder();
240
+ const keyMaterial = await window.crypto.subtle.importKey(
241
+ "raw",
242
+ encoder.encode(pin),
243
+ { name: "PBKDF2" },
244
+ false,
245
+ ["deriveBits"]
246
+ );
247
+ const derivedBits = await window.crypto.subtle.deriveBits(
248
+ {
249
+ name: "PBKDF2",
250
+ salt: encoder.encode(deviceFingerprint),
251
+ iterations: 1e5,
252
+ // High iteration count for security
253
+ hash: "SHA-256"
254
+ },
255
+ keyMaterial,
256
+ 256
257
+ // 256 bits = 32 bytes for AES-256
258
+ );
259
+ return new Uint8Array(derivedBits);
260
+ } else {
261
+ const salt = crypto2.createHash("sha256").update(deviceFingerprint).digest();
262
+ return crypto2.pbkdf2Sync(pin, salt, 1e5, 32, "sha256");
263
+ }
264
+ }
265
+ /**
266
+ * Generate random nonce for AES-GCM (12 bytes)
267
+ */
268
+ static generateNonce() {
269
+ if (typeof window !== "undefined" && window.crypto) {
270
+ return window.crypto.getRandomValues(new Uint8Array(12));
271
+ } else {
272
+ return crypto2.randomBytes(12);
273
+ }
274
+ }
275
+ /**
276
+ * Encrypt with AES-256-GCM
277
+ */
278
+ static async aesEncrypt(plaintext, key, nonce) {
279
+ if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
280
+ const keyBuffer = key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength);
281
+ const nonceBuffer = nonce.buffer.slice(nonce.byteOffset, nonce.byteOffset + nonce.byteLength);
282
+ const plaintextBuffer = plaintext.buffer.slice(plaintext.byteOffset, plaintext.byteOffset + plaintext.byteLength);
283
+ const cryptoKey = await window.crypto.subtle.importKey(
284
+ "raw",
285
+ keyBuffer,
286
+ { name: "AES-GCM" },
287
+ false,
288
+ ["encrypt"]
289
+ );
290
+ const encrypted = await window.crypto.subtle.encrypt(
291
+ {
292
+ name: "AES-GCM",
293
+ iv: nonceBuffer
294
+ },
295
+ cryptoKey,
296
+ plaintextBuffer
297
+ );
298
+ return new Uint8Array(encrypted);
299
+ } else {
300
+ const cipher = crypto2.createCipheriv("aes-256-gcm", key, nonce);
301
+ const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
302
+ const authTag = cipher.getAuthTag();
303
+ return new Uint8Array(Buffer.concat([encrypted, authTag]));
304
+ }
305
+ }
306
+ /**
307
+ * Decrypt with AES-256-GCM
308
+ */
309
+ static async aesDecrypt(ciphertext, key, nonce) {
310
+ if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
311
+ const keyBuffer = key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength);
312
+ const nonceBuffer = nonce.buffer.slice(nonce.byteOffset, nonce.byteOffset + nonce.byteLength);
313
+ const ciphertextBuffer = ciphertext.buffer.slice(ciphertext.byteOffset, ciphertext.byteOffset + ciphertext.byteLength);
314
+ const cryptoKey = await window.crypto.subtle.importKey(
315
+ "raw",
316
+ keyBuffer,
317
+ { name: "AES-GCM" },
318
+ false,
319
+ ["decrypt"]
320
+ );
321
+ const decrypted = await window.crypto.subtle.decrypt(
322
+ {
323
+ name: "AES-GCM",
324
+ iv: nonceBuffer
325
+ },
326
+ cryptoKey,
327
+ ciphertextBuffer
328
+ );
329
+ return new Uint8Array(decrypted);
330
+ } else {
331
+ const authTag = ciphertext.slice(-16);
332
+ const encrypted = ciphertext.slice(0, -16);
333
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", key, nonce);
334
+ decipher.setAuthTag(authTag);
335
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
336
+ return new Uint8Array(decrypted);
337
+ }
338
+ }
339
+ };
340
+ RecoveryQRGenerator = class {
341
+ /**
342
+ * Generate recovery QR data
343
+ * This allows users to recover their session key on a new device
344
+ */
345
+ static generate(encrypted) {
346
+ return {
347
+ encryptedSessionKey: encrypted.encryptedData,
348
+ nonce: encrypted.nonce,
349
+ publicKey: encrypted.publicKey,
350
+ version: "v1",
351
+ createdAt: Date.now()
352
+ };
353
+ }
354
+ /**
355
+ * Encode recovery QR as JSON string
356
+ */
357
+ static encode(recoveryQR) {
358
+ return JSON.stringify(recoveryQR);
359
+ }
360
+ /**
361
+ * Decode recovery QR from JSON string
362
+ */
363
+ static decode(qrData) {
364
+ try {
365
+ const parsed = JSON.parse(qrData);
366
+ if (!parsed.encryptedSessionKey || !parsed.nonce || !parsed.publicKey) {
367
+ throw new Error("Invalid recovery QR data");
368
+ }
369
+ return parsed;
370
+ } catch (error) {
371
+ throw new Error("Failed to decode recovery QR");
372
+ }
373
+ }
374
+ /**
375
+ * Re-encrypt session key for new device
376
+ */
377
+ static async reEncryptForNewDevice(recoveryQR, oldPin, oldDeviceFingerprint, newPin, newDeviceFingerprint) {
378
+ const oldEncrypted = {
379
+ encryptedData: recoveryQR.encryptedSessionKey,
380
+ nonce: recoveryQR.nonce,
381
+ publicKey: recoveryQR.publicKey,
382
+ deviceFingerprint: oldDeviceFingerprint,
383
+ version: "argon2id-aes256gcm-v1"
384
+ };
385
+ const keypair = await SessionKeyCrypto.decrypt(oldEncrypted, oldPin, oldDeviceFingerprint);
386
+ return await SessionKeyCrypto.encrypt(keypair, newPin, newDeviceFingerprint);
387
+ }
388
+ };
389
+ DeviceBoundSessionKey = class _DeviceBoundSessionKey {
390
+ encrypted = null;
391
+ deviceFingerprint = null;
392
+ sessionKeyId = null;
393
+ recoveryQR = null;
394
+ // Auto-signing cache: decrypted keypair stored in memory
395
+ // Enables instant signing without re-entering PIN for subsequent payments
396
+ cachedKeypair = null;
397
+ cacheExpiry = null;
398
+ // Timestamp when cache expires
399
+ DEFAULT_CACHE_TTL_MS = 30 * 60 * 1e3;
400
+ // 30 minutes
401
+ /**
402
+ * Create a new device-bound session key
403
+ */
404
+ static async create(options) {
405
+ const deviceFingerprint = await DeviceFingerprintGenerator.generate();
406
+ const keypair = import_web3.Keypair.generate();
407
+ const encrypted = await SessionKeyCrypto.encrypt(
408
+ keypair,
409
+ options.pin,
410
+ deviceFingerprint.fingerprint
411
+ );
412
+ const instance = new _DeviceBoundSessionKey();
413
+ instance.encrypted = encrypted;
414
+ instance.deviceFingerprint = deviceFingerprint;
415
+ if (options.generateRecoveryQR) {
416
+ instance.recoveryQR = RecoveryQRGenerator.generate(encrypted);
417
+ }
418
+ return instance;
419
+ }
420
+ /**
421
+ * Get encrypted data for backend storage
422
+ */
423
+ getEncryptedData() {
424
+ if (!this.encrypted) {
425
+ throw new Error("Session key not created yet");
426
+ }
427
+ return this.encrypted;
428
+ }
429
+ /**
430
+ * Get device fingerprint
431
+ */
432
+ getDeviceFingerprint() {
433
+ if (!this.deviceFingerprint) {
434
+ throw new Error("Device fingerprint not generated yet");
435
+ }
436
+ return this.deviceFingerprint.fingerprint;
437
+ }
438
+ /**
439
+ * Get public key
440
+ */
441
+ getPublicKey() {
442
+ if (!this.encrypted) {
443
+ throw new Error("Session key not created yet");
444
+ }
445
+ return this.encrypted.publicKey;
446
+ }
447
+ /**
448
+ * Get recovery QR data (if generated)
449
+ */
450
+ getRecoveryQR() {
451
+ return this.recoveryQR;
452
+ }
453
+ /**
454
+ * Decrypt and sign a transaction
455
+ *
456
+ * @param transaction - The transaction to sign
457
+ * @param pin - User's PIN (required only if keypair not cached)
458
+ * @param cacheKeypair - Whether to cache the decrypted keypair for future use (default: true)
459
+ * @param cacheTTL - Cache time-to-live in milliseconds (default: 30 minutes)
460
+ *
461
+ * @example
462
+ * ```typescript
463
+ * // First payment: requires PIN, caches keypair
464
+ * await sessionKey.signTransaction(tx1, '123456', true);
465
+ *
466
+ * // Subsequent payments: uses cached keypair, no PIN needed!
467
+ * await sessionKey.signTransaction(tx2, '', false); // PIN ignored if cached
468
+ *
469
+ * // Clear cache when done
470
+ * sessionKey.clearCache();
471
+ * ```
472
+ */
473
+ async signTransaction(transaction, pin = "", cacheKeypair = true, cacheTTL) {
474
+ if (!this.encrypted || !this.deviceFingerprint) {
475
+ throw new Error("Session key not initialized");
476
+ }
477
+ let keypair;
478
+ if (this.isCached()) {
479
+ keypair = this.cachedKeypair;
480
+ if (typeof console !== "undefined") {
481
+ console.log("\u{1F680} Using cached keypair - instant signing (no PIN required)");
482
+ }
483
+ } else {
484
+ if (!pin) {
485
+ throw new Error("PIN required: no cached keypair available");
486
+ }
487
+ keypair = await SessionKeyCrypto.decrypt(
488
+ this.encrypted,
489
+ pin,
490
+ this.deviceFingerprint.fingerprint
491
+ );
492
+ if (cacheKeypair) {
493
+ const ttl = cacheTTL || this.DEFAULT_CACHE_TTL_MS;
494
+ this.cacheKeypair(keypair, ttl);
495
+ if (typeof console !== "undefined") {
496
+ console.log(`\u2705 Keypair decrypted and cached for ${ttl / 1e3 / 60} minutes`);
497
+ }
498
+ }
499
+ }
500
+ transaction.sign(keypair);
501
+ return transaction;
502
+ }
503
+ /**
504
+ * Check if keypair is cached and valid
505
+ */
506
+ isCached() {
507
+ if (!this.cachedKeypair || !this.cacheExpiry) {
508
+ return false;
509
+ }
510
+ const now = Date.now();
511
+ if (now > this.cacheExpiry) {
512
+ this.clearCache();
513
+ return false;
514
+ }
515
+ return true;
516
+ }
517
+ /**
518
+ * Manually cache a keypair
519
+ * Called internally after PIN decryption
520
+ */
521
+ cacheKeypair(keypair, ttl) {
522
+ this.cachedKeypair = keypair;
523
+ this.cacheExpiry = Date.now() + ttl;
524
+ }
525
+ /**
526
+ * Clear cached keypair
527
+ * Should be called when user logs out or session ends
528
+ *
529
+ * @example
530
+ * ```typescript
531
+ * // Clear cache on logout
532
+ * sessionKey.clearCache();
533
+ *
534
+ * // Or clear automatically on tab close
535
+ * window.addEventListener('beforeunload', () => {
536
+ * sessionKey.clearCache();
537
+ * });
538
+ * ```
539
+ */
540
+ clearCache() {
541
+ this.cachedKeypair = null;
542
+ this.cacheExpiry = null;
543
+ if (typeof console !== "undefined") {
544
+ console.log("\u{1F9F9} Keypair cache cleared");
545
+ }
546
+ }
547
+ /**
548
+ * Decrypt and cache keypair without signing a transaction
549
+ * Useful for pre-warming the cache before user makes payments
550
+ *
551
+ * @example
552
+ * ```typescript
553
+ * // After session key creation, decrypt and cache
554
+ * await sessionKey.unlockWithPin('123456');
555
+ *
556
+ * // Now all subsequent payments are instant (no PIN)
557
+ * await sessionKey.signTransaction(tx1, '', false); // Instant!
558
+ * await sessionKey.signTransaction(tx2, '', false); // Instant!
559
+ * ```
560
+ */
561
+ async unlockWithPin(pin, cacheTTL) {
562
+ if (!this.encrypted || !this.deviceFingerprint) {
563
+ throw new Error("Session key not initialized");
564
+ }
565
+ const keypair = await SessionKeyCrypto.decrypt(
566
+ this.encrypted,
567
+ pin,
568
+ this.deviceFingerprint.fingerprint
569
+ );
570
+ const ttl = cacheTTL || this.DEFAULT_CACHE_TTL_MS;
571
+ this.cacheKeypair(keypair, ttl);
572
+ if (typeof console !== "undefined") {
573
+ console.log(`\u{1F513} Session key unlocked and cached for ${ttl / 1e3 / 60} minutes`);
574
+ }
575
+ }
576
+ /**
577
+ * Get time remaining until cache expires (in milliseconds)
578
+ * Returns 0 if not cached
579
+ */
580
+ getCacheTimeRemaining() {
581
+ if (!this.isCached() || !this.cacheExpiry) {
582
+ return 0;
583
+ }
584
+ const remaining = this.cacheExpiry - Date.now();
585
+ return Math.max(0, remaining);
586
+ }
587
+ /**
588
+ * Extend cache expiry time
589
+ * Useful to keep session active during user activity
590
+ *
591
+ * @example
592
+ * ```typescript
593
+ * // Extend cache by 15 minutes on each payment
594
+ * await sessionKey.signTransaction(tx, '');
595
+ * sessionKey.extendCache(15 * 60 * 1000);
596
+ * ```
597
+ */
598
+ extendCache(additionalTTL) {
599
+ if (!this.isCached()) {
600
+ throw new Error("Cannot extend cache: no cached keypair");
601
+ }
602
+ this.cacheExpiry += additionalTTL;
603
+ if (typeof console !== "undefined") {
604
+ const remainingMinutes = this.getCacheTimeRemaining() / 1e3 / 60;
605
+ console.log(`\u23F0 Cache extended - ${remainingMinutes.toFixed(1)} minutes remaining`);
606
+ }
607
+ }
608
+ /**
609
+ * Set session key ID after backend creation
610
+ */
611
+ setSessionKeyId(id) {
612
+ this.sessionKeyId = id;
613
+ }
614
+ /**
615
+ * Get session key ID
616
+ */
617
+ getSessionKeyId() {
618
+ if (!this.sessionKeyId) {
619
+ throw new Error("Session key not registered with backend");
620
+ }
621
+ return this.sessionKeyId;
622
+ }
623
+ };
624
+ }
625
+ });
626
+
627
+ // src/helpers/cache.ts
628
+ var cache_exports = {};
629
+ __export(cache_exports, {
630
+ QuickCaches: () => QuickCaches,
631
+ SessionKeyCache: () => SessionKeyCache
632
+ });
633
+ var SessionKeyCache, QuickCaches;
634
+ var init_cache = __esm({
635
+ "src/helpers/cache.ts"() {
636
+ "use strict";
637
+ SessionKeyCache = class {
638
+ memoryCache = /* @__PURE__ */ new Map();
639
+ config;
640
+ refreshTimers = /* @__PURE__ */ new Map();
641
+ constructor(config = {}) {
642
+ this.config = {
643
+ storage: config.storage || "memory",
644
+ ttl: config.ttl || 30 * 60 * 1e3,
645
+ // 30 minutes default
646
+ autoRefresh: config.autoRefresh || false,
647
+ namespace: config.namespace || "zendfi_cache",
648
+ debug: config.debug || false
649
+ };
650
+ }
651
+ /**
652
+ * Get cached keypair or decrypt and cache
653
+ */
654
+ async getCached(sessionKeyId, decryptFn, options) {
655
+ this.log(`getCached: ${sessionKeyId}`);
656
+ const memoryCached = this.memoryCache.get(sessionKeyId);
657
+ if (memoryCached && Date.now() < memoryCached.expiry) {
658
+ this.log(`Memory cache HIT: ${sessionKeyId}`);
659
+ return memoryCached.keypair;
660
+ }
661
+ if (this.config.storage !== "memory") {
662
+ const persistentCached = await this.getFromStorage(sessionKeyId);
663
+ if (persistentCached && Date.now() < persistentCached.expiry) {
664
+ if (options?.deviceFingerprint && persistentCached.deviceFingerprint) {
665
+ if (options.deviceFingerprint !== persistentCached.deviceFingerprint) {
666
+ this.log(`Device fingerprint mismatch for ${sessionKeyId}`);
667
+ await this.invalidate(sessionKeyId);
668
+ return await this.decryptAndCache(sessionKeyId, decryptFn, options);
669
+ }
670
+ }
671
+ this.log(`Persistent cache HIT: ${sessionKeyId}`);
672
+ this.memoryCache.set(sessionKeyId, persistentCached);
673
+ return persistentCached.keypair;
674
+ }
675
+ }
676
+ this.log(`Cache MISS: ${sessionKeyId}`);
677
+ return await this.decryptAndCache(sessionKeyId, decryptFn, options);
678
+ }
679
+ /**
680
+ * Decrypt keypair and cache it
681
+ */
682
+ async decryptAndCache(sessionKeyId, decryptFn, options) {
683
+ const keypair = await decryptFn();
684
+ const expiry = Date.now() + this.config.ttl;
685
+ const cached = {
686
+ keypair,
687
+ expiry,
688
+ sessionKeyId,
689
+ deviceFingerprint: options?.deviceFingerprint
690
+ };
691
+ this.memoryCache.set(sessionKeyId, cached);
692
+ if (this.config.storage !== "memory") {
693
+ await this.setInStorage(sessionKeyId, cached);
694
+ }
695
+ if (this.config.autoRefresh) {
696
+ this.setupAutoRefresh(sessionKeyId, decryptFn, options);
697
+ }
698
+ this.log(`Cached: ${sessionKeyId}, expires in ${this.config.ttl}ms`);
699
+ return keypair;
700
+ }
701
+ /**
702
+ * Invalidate cached keypair
703
+ */
704
+ async invalidate(sessionKeyId) {
705
+ this.log(`Invalidating: ${sessionKeyId}`);
706
+ this.memoryCache.delete(sessionKeyId);
707
+ const timer = this.refreshTimers.get(sessionKeyId);
708
+ if (timer) {
709
+ clearTimeout(timer);
710
+ this.refreshTimers.delete(sessionKeyId);
711
+ }
712
+ if (this.config.storage !== "memory") {
713
+ await this.removeFromStorage(sessionKeyId);
714
+ }
715
+ }
716
+ /**
717
+ * Clear all cached keypairs
718
+ */
719
+ async clear() {
720
+ this.log("Clearing all cache");
721
+ this.memoryCache.clear();
722
+ for (const timer of this.refreshTimers.values()) {
723
+ clearTimeout(timer);
724
+ }
725
+ this.refreshTimers.clear();
726
+ if (this.config.storage !== "memory") {
727
+ await this.clearStorage();
728
+ }
729
+ }
730
+ /**
731
+ * Get cache statistics
732
+ */
733
+ getStats() {
734
+ const entries = Array.from(this.memoryCache.entries()).map(([id, cached]) => ({
735
+ sessionKeyId: id,
736
+ expiresIn: Math.max(0, cached.expiry - Date.now())
737
+ }));
738
+ return { size: this.memoryCache.size, entries };
739
+ }
740
+ /**
741
+ * Check if a session key is cached and valid
742
+ */
743
+ isCached(sessionKeyId) {
744
+ const cached = this.memoryCache.get(sessionKeyId);
745
+ return cached ? Date.now() < cached.expiry : false;
746
+ }
747
+ /**
748
+ * Update TTL for a cached session key
749
+ */
750
+ async extendTTL(sessionKeyId, additionalMs) {
751
+ const cached = this.memoryCache.get(sessionKeyId);
752
+ if (!cached) return false;
753
+ cached.expiry += additionalMs;
754
+ this.memoryCache.set(sessionKeyId, cached);
755
+ if (this.config.storage !== "memory") {
756
+ await this.setInStorage(sessionKeyId, cached);
757
+ }
758
+ this.log(`Extended TTL for ${sessionKeyId} by ${additionalMs}ms`);
759
+ return true;
760
+ }
761
+ // ============================================
762
+ // Storage Backend Implementations
763
+ // ============================================
764
+ async getFromStorage(sessionKeyId) {
765
+ try {
766
+ const key = this.getStorageKey(sessionKeyId);
767
+ if (this.config.storage === "localStorage") {
768
+ const data = localStorage.getItem(key);
769
+ if (!data) return null;
770
+ const parsed = JSON.parse(data);
771
+ return {
772
+ ...parsed,
773
+ keypair: this.deserializeKeypair(parsed.keypair)
774
+ };
775
+ }
776
+ if (this.config.storage === "indexedDB") {
777
+ return await this.getFromIndexedDB(key);
778
+ }
779
+ if (typeof this.config.storage === "object") {
780
+ const data = await this.config.storage.get(key);
781
+ if (!data) return null;
782
+ const parsed = JSON.parse(data);
783
+ return {
784
+ ...parsed,
785
+ keypair: this.deserializeKeypair(parsed.keypair)
786
+ };
787
+ }
788
+ } catch (error) {
789
+ this.log(`Error reading from storage: ${error}`);
790
+ }
791
+ return null;
792
+ }
793
+ async setInStorage(sessionKeyId, cached) {
794
+ try {
795
+ const key = this.getStorageKey(sessionKeyId);
796
+ const serialized = {
797
+ ...cached,
798
+ keypair: this.serializeKeypair(cached.keypair)
799
+ };
800
+ if (this.config.storage === "localStorage") {
801
+ localStorage.setItem(key, JSON.stringify(serialized));
802
+ return;
803
+ }
804
+ if (this.config.storage === "indexedDB") {
805
+ await this.setInIndexedDB(key, serialized);
806
+ return;
807
+ }
808
+ if (typeof this.config.storage === "object") {
809
+ await this.config.storage.set(key, JSON.stringify(serialized));
810
+ }
811
+ } catch (error) {
812
+ this.log(`Error writing to storage: ${error}`);
813
+ }
814
+ }
815
+ async removeFromStorage(sessionKeyId) {
816
+ try {
817
+ const key = this.getStorageKey(sessionKeyId);
818
+ if (this.config.storage === "localStorage") {
819
+ localStorage.removeItem(key);
820
+ return;
821
+ }
822
+ if (this.config.storage === "indexedDB") {
823
+ await this.removeFromIndexedDB(key);
824
+ return;
825
+ }
826
+ if (typeof this.config.storage === "object") {
827
+ await this.config.storage.remove(key);
828
+ }
829
+ } catch (error) {
830
+ this.log(`Error removing from storage: ${error}`);
831
+ }
832
+ }
833
+ async clearStorage() {
834
+ try {
835
+ if (this.config.storage === "localStorage") {
836
+ const keysToRemove = [];
837
+ for (let i = 0; i < localStorage.length; i++) {
838
+ const key = localStorage.key(i);
839
+ if (key?.startsWith(this.config.namespace)) {
840
+ keysToRemove.push(key);
841
+ }
842
+ }
843
+ keysToRemove.forEach((key) => localStorage.removeItem(key));
844
+ return;
845
+ }
846
+ if (this.config.storage === "indexedDB") {
847
+ await this.clearIndexedDB();
848
+ return;
849
+ }
850
+ if (typeof this.config.storage === "object") {
851
+ await this.config.storage.clear();
852
+ }
853
+ } catch (error) {
854
+ this.log(`Error clearing storage: ${error}`);
855
+ }
856
+ }
857
+ // ============================================
858
+ // IndexedDB Helpers
859
+ // ============================================
860
+ async getFromIndexedDB(key) {
861
+ return new Promise((resolve) => {
862
+ const request = indexedDB.open(this.config.namespace, 1);
863
+ request.onerror = () => resolve(null);
864
+ request.onupgradeneeded = (event) => {
865
+ const db = event.target.result;
866
+ if (!db.objectStoreNames.contains("cache")) {
867
+ db.createObjectStore("cache");
868
+ }
869
+ };
870
+ request.onsuccess = (event) => {
871
+ const db = event.target.result;
872
+ const transaction = db.transaction(["cache"], "readonly");
873
+ const store = transaction.objectStore("cache");
874
+ const getRequest = store.get(key);
875
+ getRequest.onsuccess = () => {
876
+ resolve(getRequest.result || null);
877
+ };
878
+ getRequest.onerror = () => resolve(null);
879
+ };
880
+ });
881
+ }
882
+ async setInIndexedDB(key, value) {
883
+ return new Promise((resolve, reject) => {
884
+ const request = indexedDB.open(this.config.namespace, 1);
885
+ request.onerror = () => reject(new Error("IndexedDB error"));
886
+ request.onupgradeneeded = (event) => {
887
+ const db = event.target.result;
888
+ if (!db.objectStoreNames.contains("cache")) {
889
+ db.createObjectStore("cache");
890
+ }
891
+ };
892
+ request.onsuccess = (event) => {
893
+ const db = event.target.result;
894
+ const transaction = db.transaction(["cache"], "readwrite");
895
+ const store = transaction.objectStore("cache");
896
+ store.put(value, key);
897
+ transaction.oncomplete = () => resolve();
898
+ transaction.onerror = () => reject(new Error("IndexedDB transaction error"));
899
+ };
900
+ });
901
+ }
902
+ async removeFromIndexedDB(key) {
903
+ return new Promise((resolve) => {
904
+ const request = indexedDB.open(this.config.namespace, 1);
905
+ request.onsuccess = (event) => {
906
+ const db = event.target.result;
907
+ const transaction = db.transaction(["cache"], "readwrite");
908
+ const store = transaction.objectStore("cache");
909
+ store.delete(key);
910
+ transaction.oncomplete = () => resolve();
911
+ };
912
+ request.onerror = () => resolve();
913
+ });
914
+ }
915
+ async clearIndexedDB() {
916
+ return new Promise((resolve) => {
917
+ const request = indexedDB.open(this.config.namespace, 1);
918
+ request.onsuccess = (event) => {
919
+ const db = event.target.result;
920
+ const transaction = db.transaction(["cache"], "readwrite");
921
+ const store = transaction.objectStore("cache");
922
+ store.clear();
923
+ transaction.oncomplete = () => resolve();
924
+ };
925
+ request.onerror = () => resolve();
926
+ });
927
+ }
928
+ // ============================================
929
+ // Serialization
930
+ // ============================================
931
+ serializeKeypair(keypair) {
932
+ if (keypair && typeof keypair === "object" && "secretKey" in keypair) {
933
+ return {
934
+ type: "solana",
935
+ secretKey: Array.from(keypair.secretKey)
936
+ };
937
+ }
938
+ if (keypair instanceof Uint8Array) {
939
+ return {
940
+ type: "uint8array",
941
+ data: Array.from(keypair)
942
+ };
943
+ }
944
+ return keypair;
945
+ }
946
+ deserializeKeypair(data) {
947
+ if (!data || typeof data !== "object") return data;
948
+ if (data.type === "solana" && data.secretKey) {
949
+ return new Uint8Array(data.secretKey);
950
+ }
951
+ if (data.type === "uint8array" && data.data) {
952
+ return new Uint8Array(data.data);
953
+ }
954
+ return data;
955
+ }
956
+ // ============================================
957
+ // Auto-Refresh
958
+ // ============================================
959
+ setupAutoRefresh(sessionKeyId, decryptFn, options) {
960
+ const existingTimer = this.refreshTimers.get(sessionKeyId);
961
+ if (existingTimer) {
962
+ clearTimeout(existingTimer);
963
+ }
964
+ const refreshIn = Math.max(0, this.config.ttl - 5 * 60 * 1e3);
965
+ const timer = setTimeout(async () => {
966
+ this.log(`Auto-refreshing: ${sessionKeyId}`);
967
+ try {
968
+ await this.decryptAndCache(sessionKeyId, decryptFn, options);
969
+ } catch (error) {
970
+ this.log(`Auto-refresh failed: ${error}`);
971
+ }
972
+ }, refreshIn);
973
+ this.refreshTimers.set(sessionKeyId, timer);
974
+ }
975
+ // ============================================
976
+ // Utilities
977
+ // ============================================
978
+ getStorageKey(sessionKeyId) {
979
+ return `${this.config.namespace}:${sessionKeyId}`;
980
+ }
981
+ log(message) {
982
+ if (this.config.debug) {
983
+ console.log(`[SessionKeyCache] ${message}`);
984
+ }
985
+ }
986
+ };
987
+ QuickCaches = {
988
+ /** Memory-only cache (30 minutes) */
989
+ memory: () => new SessionKeyCache({ storage: "memory", ttl: 30 * 60 * 1e3 }),
990
+ /** Persistent cache (1 hour, survives reload) */
991
+ persistent: () => new SessionKeyCache({ storage: "localStorage", ttl: 60 * 60 * 1e3 }),
992
+ /** Long-term cache (24 hours, IndexedDB) */
993
+ longTerm: () => new SessionKeyCache({ storage: "indexedDB", ttl: 24 * 60 * 60 * 1e3, autoRefresh: true }),
994
+ /** Secure cache (5 minutes, memory-only) */
995
+ secure: () => new SessionKeyCache({ storage: "memory", ttl: 5 * 60 * 1e3 })
996
+ };
997
+ }
998
+ });
999
+
30
1000
  // src/index.ts
31
1001
  var index_exports = {};
32
1002
  __export(index_exports, {
33
1003
  AgentAPI: () => AgentAPI,
1004
+ AnthropicAdapter: () => AnthropicAdapter,
34
1005
  ApiError: () => ApiError,
35
1006
  AuthenticationError: () => AuthenticationError2,
36
1007
  AutonomyAPI: () => AutonomyAPI,
37
1008
  ConfigLoader: () => ConfigLoader,
1009
+ DevTools: () => DevTools,
38
1010
  DeviceBoundSessionKey: () => DeviceBoundSessionKey,
39
1011
  DeviceFingerprintGenerator: () => DeviceFingerprintGenerator,
40
1012
  ERROR_CODES: () => ERROR_CODES,
1013
+ ErrorRecovery: () => ErrorRecovery,
1014
+ GeminiAdapter: () => GeminiAdapter,
41
1015
  InterceptorManager: () => InterceptorManager,
42
1016
  LitCryptoSigner: () => LitCryptoSigner,
43
1017
  NetworkError: () => NetworkError2,
1018
+ OpenAIAdapter: () => OpenAIAdapter,
1019
+ PINRateLimiter: () => PINRateLimiter,
1020
+ PINValidator: () => PINValidator,
44
1021
  PaymentError: () => PaymentError,
1022
+ PaymentIntentParser: () => PaymentIntentParser,
45
1023
  PaymentIntentsAPI: () => PaymentIntentsAPI,
1024
+ PerformanceMonitor: () => PerformanceMonitor,
46
1025
  PricingAPI: () => PricingAPI,
1026
+ QuickCaches: () => QuickCaches,
47
1027
  RateLimitError: () => RateLimitError2,
48
1028
  RateLimiter: () => RateLimiter,
49
1029
  RecoveryQRGenerator: () => RecoveryQRGenerator,
1030
+ RetryStrategy: () => RetryStrategy,
50
1031
  SPENDING_LIMIT_ACTION_CID: () => SPENDING_LIMIT_ACTION_CID,
1032
+ SecureStorage: () => SecureStorage,
1033
+ SessionKeyCache: () => SessionKeyCache,
51
1034
  SessionKeyCrypto: () => SessionKeyCrypto,
1035
+ SessionKeyLifecycle: () => SessionKeyLifecycle,
52
1036
  SessionKeysAPI: () => SessionKeysAPI,
53
1037
  SmartPaymentsAPI: () => SmartPaymentsAPI,
1038
+ TransactionMonitor: () => TransactionMonitor,
1039
+ TransactionPoller: () => TransactionPoller,
54
1040
  ValidationError: () => ValidationError2,
1041
+ WalletConnector: () => WalletConnector,
55
1042
  WebhookError: () => WebhookError,
56
1043
  ZendFiClient: () => ZendFiClient,
57
1044
  ZendFiError: () => ZendFiError2,
@@ -66,6 +1053,7 @@ __export(index_exports, {
66
1053
  asPaymentLinkCode: () => asPaymentLinkCode,
67
1054
  asSessionId: () => asSessionId,
68
1055
  asSubscriptionId: () => asSubscriptionId,
1056
+ createWalletHook: () => createWalletHook,
69
1057
  createZendFiError: () => createZendFiError,
70
1058
  decodeSignatureFromLit: () => decodeSignatureFromLit,
71
1059
  encodeTransactionForLit: () => encodeTransactionForLit,
@@ -73,6 +1061,7 @@ __export(index_exports, {
73
1061
  isZendFiError: () => isZendFiError,
74
1062
  processWebhook: () => processWebhook,
75
1063
  requiresLitSigning: () => requiresLitSigning,
1064
+ setupQuickSessionKey: () => setupQuickSessionKey,
76
1065
  sleep: () => sleep,
77
1066
  verifyExpressWebhook: () => verifyExpressWebhook,
78
1067
  verifyNextWebhook: () => verifyNextWebhook,
@@ -1277,6 +2266,43 @@ var AutonomyAPI = class {
1277
2266
  throw new Error("delegation_signature must be base64 encoded");
1278
2267
  }
1279
2268
  }
2269
+ /**
2270
+ * Get spending attestations for a delegate (audit trail)
2271
+ *
2272
+ * Returns all cryptographically signed attestations ZendFi created for
2273
+ * this delegate. Each attestation contains:
2274
+ * - The spending state at the time of payment
2275
+ * - ZendFi's Ed25519 signature
2276
+ * - Timestamp and nonce (for replay protection)
2277
+ *
2278
+ * These attestations can be independently verified using ZendFi's public key
2279
+ * to confirm spending limit enforcement was applied correctly.
2280
+ *
2281
+ * @param delegateId - UUID of the autonomous delegate
2282
+ * @returns Attestation audit response with all signed attestations
2283
+ *
2284
+ * @example
2285
+ * ```typescript
2286
+ * const audit = await zendfi.autonomy.getAttestations('delegate_123...');
2287
+ *
2288
+ * console.log(`Found ${audit.attestation_count} attestations`);
2289
+ * console.log(`ZendFi public key: ${audit.zendfi_attestation_public_key}`);
2290
+ *
2291
+ * // Verify each attestation independently
2292
+ * for (const signed of audit.attestations) {
2293
+ * console.log(`Payment ${signed.attestation.payment_id}:`);
2294
+ * console.log(` Requested: $${signed.attestation.requested_usd}`);
2295
+ * console.log(` Remaining after: $${signed.attestation.remaining_after_usd}`);
2296
+ * // Verify signature with nacl.sign.detached.verify()
2297
+ * }
2298
+ * ```
2299
+ */
2300
+ async getAttestations(delegateId) {
2301
+ return this.request(
2302
+ "GET",
2303
+ `/api/v1/ai/delegates/${delegateId}/attestations`
2304
+ );
2305
+ }
1280
2306
  };
1281
2307
 
1282
2308
  // src/api/smart-payments.ts
@@ -2464,616 +3490,39 @@ function verifyWebhookSignature(payload, signature, secret) {
2464
3490
  });
2465
3491
  }
2466
3492
 
2467
- // src/device-bound-crypto.ts
2468
- var import_web3 = require("@solana/web3.js");
2469
- var crypto = __toESM(require("crypto"));
2470
- var DeviceFingerprintGenerator = class {
2471
- /**
2472
- * Generate a unique device fingerprint
2473
- * Combines multiple browser attributes for uniqueness
2474
- */
2475
- static async generate() {
2476
- const components = {};
2477
- try {
2478
- components.canvas = await this.getCanvasFingerprint();
2479
- components.webgl = await this.getWebGLFingerprint();
2480
- components.audio = await this.getAudioFingerprint();
2481
- components.screen = `${screen.width}x${screen.height}x${screen.colorDepth}`;
2482
- components.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
2483
- components.languages = navigator.languages?.join(",") || navigator.language;
2484
- components.platform = navigator.platform;
2485
- components.hardwareConcurrency = navigator.hardwareConcurrency?.toString() || "unknown";
2486
- const combined = Object.entries(components).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}:${value}`).join("|");
2487
- const fingerprint = await this.sha256(combined);
2488
- return {
2489
- fingerprint,
2490
- generatedAt: Date.now(),
2491
- components
2492
- };
2493
- } catch (error) {
2494
- console.warn("Device fingerprinting failed, using fallback", error);
2495
- return this.generateFallbackFingerprint();
2496
- }
3493
+ // src/device-bound-session-keys.ts
3494
+ init_device_bound_crypto();
3495
+ var import_web32 = require("@solana/web3.js");
3496
+ var ZendFiSessionKeyManager = class {
3497
+ baseURL;
3498
+ apiKey;
3499
+ sessionKey = null;
3500
+ sessionKeyId = null;
3501
+ constructor(apiKey, baseURL = "https://api.zendfi.com") {
3502
+ this.apiKey = apiKey;
3503
+ this.baseURL = baseURL;
2497
3504
  }
2498
3505
  /**
2499
- * Graceful fallback fingerprint generation
2500
- * Works in headless browsers, SSR, and restricted environments
2501
- */
2502
- static async generateFallbackFingerprint() {
2503
- const components = {};
2504
- try {
2505
- if (typeof navigator !== "undefined") {
2506
- components.platform = navigator.platform || "unknown";
2507
- components.languages = navigator.languages?.join(",") || navigator.language || "unknown";
2508
- components.hardwareConcurrency = navigator.hardwareConcurrency?.toString() || "unknown";
2509
- }
2510
- if (typeof screen !== "undefined") {
2511
- components.screen = `${screen.width || 0}x${screen.height || 0}x${screen.colorDepth || 0}`;
2512
- }
2513
- if (typeof Intl !== "undefined") {
2514
- try {
2515
- components.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
2516
- } catch {
2517
- components.timezone = "unknown";
2518
- }
2519
- }
2520
- } catch {
2521
- components.platform = "fallback";
2522
- }
2523
- let randomEntropy = "";
2524
- try {
2525
- if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
2526
- const arr = new Uint8Array(16);
2527
- window.crypto.getRandomValues(arr);
2528
- randomEntropy = Array.from(arr).map((b) => b.toString(16).padStart(2, "0")).join("");
2529
- } else if (typeof crypto !== "undefined" && crypto.randomBytes) {
2530
- randomEntropy = crypto.randomBytes(16).toString("hex");
2531
- }
2532
- } catch {
2533
- randomEntropy = Date.now().toString(36) + Math.random().toString(36);
2534
- }
2535
- const combined = Object.entries(components).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}:${value}`).join("|") + "|entropy:" + randomEntropy;
2536
- const fingerprint = await this.sha256(combined);
2537
- return {
2538
- fingerprint,
2539
- generatedAt: Date.now(),
2540
- components
2541
- };
2542
- }
2543
- static async getCanvasFingerprint() {
2544
- const canvas = document.createElement("canvas");
2545
- const ctx = canvas.getContext("2d");
2546
- if (!ctx) return "no-canvas";
2547
- canvas.width = 200;
2548
- canvas.height = 50;
2549
- ctx.textBaseline = "top";
2550
- ctx.font = '14px "Arial"';
2551
- ctx.fillStyle = "#f60";
2552
- ctx.fillRect(0, 0, 100, 50);
2553
- ctx.fillStyle = "#069";
2554
- ctx.fillText("ZendFi \u{1F510}", 2, 2);
2555
- ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
2556
- ctx.fillText("Device-Bound", 4, 17);
2557
- return canvas.toDataURL();
2558
- }
2559
- static async getWebGLFingerprint() {
2560
- const canvas = document.createElement("canvas");
2561
- const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
2562
- if (!gl) return "no-webgl";
2563
- const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
2564
- if (!debugInfo) return "no-debug-info";
2565
- const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
2566
- const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
2567
- return `${vendor}|${renderer}`;
2568
- }
2569
- static async getAudioFingerprint() {
2570
- try {
2571
- const AudioContext = window.AudioContext || window.webkitAudioContext;
2572
- if (!AudioContext) return "no-audio";
2573
- const context = new AudioContext();
2574
- const oscillator = context.createOscillator();
2575
- const analyser = context.createAnalyser();
2576
- const gainNode = context.createGain();
2577
- const scriptProcessor = context.createScriptProcessor(4096, 1, 1);
2578
- gainNode.gain.value = 0;
2579
- oscillator.connect(analyser);
2580
- analyser.connect(scriptProcessor);
2581
- scriptProcessor.connect(gainNode);
2582
- gainNode.connect(context.destination);
2583
- oscillator.start(0);
2584
- return new Promise((resolve) => {
2585
- scriptProcessor.onaudioprocess = (event) => {
2586
- const output = event.inputBuffer.getChannelData(0);
2587
- const hash = Array.from(output.slice(0, 30)).reduce((acc, val) => acc + Math.abs(val), 0);
2588
- oscillator.stop();
2589
- scriptProcessor.disconnect();
2590
- context.close();
2591
- resolve(hash.toString());
2592
- };
2593
- });
2594
- } catch (error) {
2595
- return "audio-error";
2596
- }
2597
- }
2598
- static async sha256(data) {
2599
- if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
2600
- const encoder = new TextEncoder();
2601
- const dataBuffer = encoder.encode(data);
2602
- const hashBuffer = await window.crypto.subtle.digest("SHA-256", dataBuffer);
2603
- const hashArray = Array.from(new Uint8Array(hashBuffer));
2604
- return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
2605
- } else {
2606
- return crypto.createHash("sha256").update(data).digest("hex");
2607
- }
2608
- }
2609
- };
2610
- var SessionKeyCrypto = class {
2611
- /**
2612
- * Encrypt a Solana keypair with PIN + device fingerprint
2613
- * Uses Argon2id for key derivation and AES-256-GCM for encryption
2614
- */
2615
- static async encrypt(keypair, pin, deviceFingerprint) {
2616
- if (!/^\d{6}$/.test(pin)) {
2617
- throw new Error("PIN must be exactly 6 numeric digits");
2618
- }
2619
- const encryptionKey = await this.deriveKey(pin, deviceFingerprint);
2620
- const nonce = this.generateNonce();
2621
- const secretKey = keypair.secretKey;
2622
- const encryptedData = await this.aesEncrypt(secretKey, encryptionKey, nonce);
2623
- return {
2624
- encryptedData: Buffer.from(encryptedData).toString("base64"),
2625
- nonce: Buffer.from(nonce).toString("base64"),
2626
- publicKey: keypair.publicKey.toBase58(),
2627
- deviceFingerprint,
2628
- version: "argon2id-aes256gcm-v1"
2629
- };
2630
- }
2631
- /**
2632
- * Decrypt an encrypted session key with PIN + device fingerprint
2633
- */
2634
- static async decrypt(encrypted, pin, deviceFingerprint) {
2635
- if (!/^\d{6}$/.test(pin)) {
2636
- throw new Error("PIN must be exactly 6 numeric digits");
2637
- }
2638
- if (encrypted.deviceFingerprint !== deviceFingerprint) {
2639
- throw new Error("Device fingerprint mismatch - wrong device or security threat");
2640
- }
2641
- const encryptionKey = await this.deriveKey(pin, deviceFingerprint);
2642
- const encryptedData = Buffer.from(encrypted.encryptedData, "base64");
2643
- const nonce = Buffer.from(encrypted.nonce, "base64");
2644
- try {
2645
- const secretKey = await this.aesDecrypt(encryptedData, encryptionKey, nonce);
2646
- return import_web3.Keypair.fromSecretKey(secretKey);
2647
- } catch (error) {
2648
- throw new Error("Decryption failed - wrong PIN or corrupted data");
2649
- }
2650
- }
2651
- /**
2652
- * Derive encryption key from PIN + device fingerprint using Argon2id
2653
- *
2654
- * Argon2id parameters (OWASP recommended):
2655
- * - Memory: 64MB (65536 KB)
2656
- * - Iterations: 3
2657
- * - Parallelism: 4
2658
- * - Salt: device fingerprint
2659
- */
2660
- static async deriveKey(pin, deviceFingerprint) {
2661
- if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
2662
- const encoder = new TextEncoder();
2663
- const keyMaterial = await window.crypto.subtle.importKey(
2664
- "raw",
2665
- encoder.encode(pin),
2666
- { name: "PBKDF2" },
2667
- false,
2668
- ["deriveBits"]
2669
- );
2670
- const derivedBits = await window.crypto.subtle.deriveBits(
2671
- {
2672
- name: "PBKDF2",
2673
- salt: encoder.encode(deviceFingerprint),
2674
- iterations: 1e5,
2675
- // High iteration count for security
2676
- hash: "SHA-256"
2677
- },
2678
- keyMaterial,
2679
- 256
2680
- // 256 bits = 32 bytes for AES-256
2681
- );
2682
- return new Uint8Array(derivedBits);
2683
- } else {
2684
- const salt = crypto.createHash("sha256").update(deviceFingerprint).digest();
2685
- return crypto.pbkdf2Sync(pin, salt, 1e5, 32, "sha256");
2686
- }
2687
- }
2688
- /**
2689
- * Generate random nonce for AES-GCM (12 bytes)
2690
- */
2691
- static generateNonce() {
2692
- if (typeof window !== "undefined" && window.crypto) {
2693
- return window.crypto.getRandomValues(new Uint8Array(12));
2694
- } else {
2695
- return crypto.randomBytes(12);
2696
- }
2697
- }
2698
- /**
2699
- * Encrypt with AES-256-GCM
2700
- */
2701
- static async aesEncrypt(plaintext, key, nonce) {
2702
- if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
2703
- const keyBuffer = key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength);
2704
- const nonceBuffer = nonce.buffer.slice(nonce.byteOffset, nonce.byteOffset + nonce.byteLength);
2705
- const plaintextBuffer = plaintext.buffer.slice(plaintext.byteOffset, plaintext.byteOffset + plaintext.byteLength);
2706
- const cryptoKey = await window.crypto.subtle.importKey(
2707
- "raw",
2708
- keyBuffer,
2709
- { name: "AES-GCM" },
2710
- false,
2711
- ["encrypt"]
2712
- );
2713
- const encrypted = await window.crypto.subtle.encrypt(
2714
- {
2715
- name: "AES-GCM",
2716
- iv: nonceBuffer
2717
- },
2718
- cryptoKey,
2719
- plaintextBuffer
2720
- );
2721
- return new Uint8Array(encrypted);
2722
- } else {
2723
- const cipher = crypto.createCipheriv("aes-256-gcm", key, nonce);
2724
- const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
2725
- const authTag = cipher.getAuthTag();
2726
- return new Uint8Array(Buffer.concat([encrypted, authTag]));
2727
- }
2728
- }
2729
- /**
2730
- * Decrypt with AES-256-GCM
2731
- */
2732
- static async aesDecrypt(ciphertext, key, nonce) {
2733
- if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
2734
- const keyBuffer = key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength);
2735
- const nonceBuffer = nonce.buffer.slice(nonce.byteOffset, nonce.byteOffset + nonce.byteLength);
2736
- const ciphertextBuffer = ciphertext.buffer.slice(ciphertext.byteOffset, ciphertext.byteOffset + ciphertext.byteLength);
2737
- const cryptoKey = await window.crypto.subtle.importKey(
2738
- "raw",
2739
- keyBuffer,
2740
- { name: "AES-GCM" },
2741
- false,
2742
- ["decrypt"]
2743
- );
2744
- const decrypted = await window.crypto.subtle.decrypt(
2745
- {
2746
- name: "AES-GCM",
2747
- iv: nonceBuffer
2748
- },
2749
- cryptoKey,
2750
- ciphertextBuffer
2751
- );
2752
- return new Uint8Array(decrypted);
2753
- } else {
2754
- const authTag = ciphertext.slice(-16);
2755
- const encrypted = ciphertext.slice(0, -16);
2756
- const decipher = crypto.createDecipheriv("aes-256-gcm", key, nonce);
2757
- decipher.setAuthTag(authTag);
2758
- const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
2759
- return new Uint8Array(decrypted);
2760
- }
2761
- }
2762
- };
2763
- var RecoveryQRGenerator = class {
2764
- /**
2765
- * Generate recovery QR data
2766
- * This allows users to recover their session key on a new device
2767
- */
2768
- static generate(encrypted) {
2769
- return {
2770
- encryptedSessionKey: encrypted.encryptedData,
2771
- nonce: encrypted.nonce,
2772
- publicKey: encrypted.publicKey,
2773
- version: "v1",
2774
- createdAt: Date.now()
2775
- };
2776
- }
2777
- /**
2778
- * Encode recovery QR as JSON string
2779
- */
2780
- static encode(recoveryQR) {
2781
- return JSON.stringify(recoveryQR);
2782
- }
2783
- /**
2784
- * Decode recovery QR from JSON string
2785
- */
2786
- static decode(qrData) {
2787
- try {
2788
- const parsed = JSON.parse(qrData);
2789
- if (!parsed.encryptedSessionKey || !parsed.nonce || !parsed.publicKey) {
2790
- throw new Error("Invalid recovery QR data");
2791
- }
2792
- return parsed;
2793
- } catch (error) {
2794
- throw new Error("Failed to decode recovery QR");
2795
- }
2796
- }
2797
- /**
2798
- * Re-encrypt session key for new device
2799
- */
2800
- static async reEncryptForNewDevice(recoveryQR, oldPin, oldDeviceFingerprint, newPin, newDeviceFingerprint) {
2801
- const oldEncrypted = {
2802
- encryptedData: recoveryQR.encryptedSessionKey,
2803
- nonce: recoveryQR.nonce,
2804
- publicKey: recoveryQR.publicKey,
2805
- deviceFingerprint: oldDeviceFingerprint,
2806
- version: "argon2id-aes256gcm-v1"
2807
- };
2808
- const keypair = await SessionKeyCrypto.decrypt(oldEncrypted, oldPin, oldDeviceFingerprint);
2809
- return await SessionKeyCrypto.encrypt(keypair, newPin, newDeviceFingerprint);
2810
- }
2811
- };
2812
- var DeviceBoundSessionKey = class _DeviceBoundSessionKey {
2813
- encrypted = null;
2814
- deviceFingerprint = null;
2815
- sessionKeyId = null;
2816
- recoveryQR = null;
2817
- // Auto-signing cache: decrypted keypair stored in memory
2818
- // Enables instant signing without re-entering PIN for subsequent payments
2819
- cachedKeypair = null;
2820
- cacheExpiry = null;
2821
- // Timestamp when cache expires
2822
- DEFAULT_CACHE_TTL_MS = 30 * 60 * 1e3;
2823
- // 30 minutes
2824
- /**
2825
- * Create a new device-bound session key
2826
- */
2827
- static async create(options) {
2828
- const deviceFingerprint = await DeviceFingerprintGenerator.generate();
2829
- const keypair = import_web3.Keypair.generate();
2830
- const encrypted = await SessionKeyCrypto.encrypt(
2831
- keypair,
2832
- options.pin,
2833
- deviceFingerprint.fingerprint
2834
- );
2835
- const instance = new _DeviceBoundSessionKey();
2836
- instance.encrypted = encrypted;
2837
- instance.deviceFingerprint = deviceFingerprint;
2838
- if (options.generateRecoveryQR) {
2839
- instance.recoveryQR = RecoveryQRGenerator.generate(encrypted);
2840
- }
2841
- return instance;
2842
- }
2843
- /**
2844
- * Get encrypted data for backend storage
2845
- */
2846
- getEncryptedData() {
2847
- if (!this.encrypted) {
2848
- throw new Error("Session key not created yet");
2849
- }
2850
- return this.encrypted;
2851
- }
2852
- /**
2853
- * Get device fingerprint
2854
- */
2855
- getDeviceFingerprint() {
2856
- if (!this.deviceFingerprint) {
2857
- throw new Error("Device fingerprint not generated yet");
2858
- }
2859
- return this.deviceFingerprint.fingerprint;
2860
- }
2861
- /**
2862
- * Get public key
2863
- */
2864
- getPublicKey() {
2865
- if (!this.encrypted) {
2866
- throw new Error("Session key not created yet");
2867
- }
2868
- return this.encrypted.publicKey;
2869
- }
2870
- /**
2871
- * Get recovery QR data (if generated)
2872
- */
2873
- getRecoveryQR() {
2874
- return this.recoveryQR;
2875
- }
2876
- /**
2877
- * Decrypt and sign a transaction
2878
- *
2879
- * @param transaction - The transaction to sign
2880
- * @param pin - User's PIN (required only if keypair not cached)
2881
- * @param cacheKeypair - Whether to cache the decrypted keypair for future use (default: true)
2882
- * @param cacheTTL - Cache time-to-live in milliseconds (default: 30 minutes)
2883
- *
2884
- * @example
2885
- * ```typescript
2886
- * // First payment: requires PIN, caches keypair
2887
- * await sessionKey.signTransaction(tx1, '123456', true);
2888
- *
2889
- * // Subsequent payments: uses cached keypair, no PIN needed!
2890
- * await sessionKey.signTransaction(tx2, '', false); // PIN ignored if cached
2891
- *
2892
- * // Clear cache when done
2893
- * sessionKey.clearCache();
2894
- * ```
2895
- */
2896
- async signTransaction(transaction, pin = "", cacheKeypair = true, cacheTTL) {
2897
- if (!this.encrypted || !this.deviceFingerprint) {
2898
- throw new Error("Session key not initialized");
2899
- }
2900
- let keypair;
2901
- if (this.isCached()) {
2902
- keypair = this.cachedKeypair;
2903
- if (typeof console !== "undefined") {
2904
- console.log("\u{1F680} Using cached keypair - instant signing (no PIN required)");
2905
- }
2906
- } else {
2907
- if (!pin) {
2908
- throw new Error("PIN required: no cached keypair available");
2909
- }
2910
- keypair = await SessionKeyCrypto.decrypt(
2911
- this.encrypted,
2912
- pin,
2913
- this.deviceFingerprint.fingerprint
2914
- );
2915
- if (cacheKeypair) {
2916
- const ttl = cacheTTL || this.DEFAULT_CACHE_TTL_MS;
2917
- this.cacheKeypair(keypair, ttl);
2918
- if (typeof console !== "undefined") {
2919
- console.log(`\u2705 Keypair decrypted and cached for ${ttl / 1e3 / 60} minutes`);
2920
- }
2921
- }
2922
- }
2923
- transaction.sign(keypair);
2924
- return transaction;
2925
- }
2926
- /**
2927
- * Check if keypair is cached and valid
2928
- */
2929
- isCached() {
2930
- if (!this.cachedKeypair || !this.cacheExpiry) {
2931
- return false;
2932
- }
2933
- const now = Date.now();
2934
- if (now > this.cacheExpiry) {
2935
- this.clearCache();
2936
- return false;
2937
- }
2938
- return true;
2939
- }
2940
- /**
2941
- * Manually cache a keypair
2942
- * Called internally after PIN decryption
2943
- */
2944
- cacheKeypair(keypair, ttl) {
2945
- this.cachedKeypair = keypair;
2946
- this.cacheExpiry = Date.now() + ttl;
2947
- }
2948
- /**
2949
- * Clear cached keypair
2950
- * Should be called when user logs out or session ends
2951
- *
2952
- * @example
2953
- * ```typescript
2954
- * // Clear cache on logout
2955
- * sessionKey.clearCache();
2956
- *
2957
- * // Or clear automatically on tab close
2958
- * window.addEventListener('beforeunload', () => {
2959
- * sessionKey.clearCache();
2960
- * });
2961
- * ```
2962
- */
2963
- clearCache() {
2964
- this.cachedKeypair = null;
2965
- this.cacheExpiry = null;
2966
- if (typeof console !== "undefined") {
2967
- console.log("\u{1F9F9} Keypair cache cleared");
2968
- }
2969
- }
2970
- /**
2971
- * Decrypt and cache keypair without signing a transaction
2972
- * Useful for pre-warming the cache before user makes payments
2973
- *
2974
- * @example
2975
- * ```typescript
2976
- * // After session key creation, decrypt and cache
2977
- * await sessionKey.unlockWithPin('123456');
2978
- *
2979
- * // Now all subsequent payments are instant (no PIN)
2980
- * await sessionKey.signTransaction(tx1, '', false); // Instant!
2981
- * await sessionKey.signTransaction(tx2, '', false); // Instant!
2982
- * ```
2983
- */
2984
- async unlockWithPin(pin, cacheTTL) {
2985
- if (!this.encrypted || !this.deviceFingerprint) {
2986
- throw new Error("Session key not initialized");
2987
- }
2988
- const keypair = await SessionKeyCrypto.decrypt(
2989
- this.encrypted,
2990
- pin,
2991
- this.deviceFingerprint.fingerprint
2992
- );
2993
- const ttl = cacheTTL || this.DEFAULT_CACHE_TTL_MS;
2994
- this.cacheKeypair(keypair, ttl);
2995
- if (typeof console !== "undefined") {
2996
- console.log(`\u{1F513} Session key unlocked and cached for ${ttl / 1e3 / 60} minutes`);
2997
- }
2998
- }
2999
- /**
3000
- * Get time remaining until cache expires (in milliseconds)
3001
- * Returns 0 if not cached
3002
- */
3003
- getCacheTimeRemaining() {
3004
- if (!this.isCached() || !this.cacheExpiry) {
3005
- return 0;
3006
- }
3007
- const remaining = this.cacheExpiry - Date.now();
3008
- return Math.max(0, remaining);
3009
- }
3010
- /**
3011
- * Extend cache expiry time
3012
- * Useful to keep session active during user activity
3013
- *
3014
- * @example
3015
- * ```typescript
3016
- * // Extend cache by 15 minutes on each payment
3017
- * await sessionKey.signTransaction(tx, '');
3018
- * sessionKey.extendCache(15 * 60 * 1000);
3019
- * ```
3020
- */
3021
- extendCache(additionalTTL) {
3022
- if (!this.isCached()) {
3023
- throw new Error("Cannot extend cache: no cached keypair");
3024
- }
3025
- this.cacheExpiry += additionalTTL;
3026
- if (typeof console !== "undefined") {
3027
- const remainingMinutes = this.getCacheTimeRemaining() / 1e3 / 60;
3028
- console.log(`\u23F0 Cache extended - ${remainingMinutes.toFixed(1)} minutes remaining`);
3029
- }
3030
- }
3031
- /**
3032
- * Set session key ID after backend creation
3033
- */
3034
- setSessionKeyId(id) {
3035
- this.sessionKeyId = id;
3036
- }
3037
- /**
3038
- * Get session key ID
3039
- */
3040
- getSessionKeyId() {
3041
- if (!this.sessionKeyId) {
3042
- throw new Error("Session key not registered with backend");
3043
- }
3044
- return this.sessionKeyId;
3045
- }
3046
- };
3047
-
3048
- // src/device-bound-session-keys.ts
3049
- var import_web32 = require("@solana/web3.js");
3050
- var ZendFiSessionKeyManager = class {
3051
- baseURL;
3052
- apiKey;
3053
- sessionKey = null;
3054
- sessionKeyId = null;
3055
- constructor(apiKey, baseURL = "https://api.zendfi.com") {
3056
- this.apiKey = apiKey;
3057
- this.baseURL = baseURL;
3058
- }
3059
- /**
3060
- * Create a new device-bound session key
3061
- *
3062
- * @example
3063
- * ```typescript
3064
- * const manager = new ZendFiSessionKeyManager('your-api-key');
3065
- *
3066
- * const sessionKey = await manager.createSessionKey({
3067
- * userWallet: '7xKNH....',
3068
- * limitUSDC: 100,
3069
- * durationDays: 7,
3070
- * pin: '123456',
3071
- * generateRecoveryQR: true,
3072
- * });
3073
- *
3074
- * console.log('Session key created:', sessionKey.sessionKeyId);
3075
- * console.log('Recovery QR:', sessionKey.recoveryQR);
3076
- * ```
3506
+ * Create a new device-bound session key
3507
+ *
3508
+ * @example
3509
+ * ```typescript
3510
+ * const manager = new ZendFiSessionKeyManager('your-api-key');
3511
+ *
3512
+ * const sessionKey = await manager.createSessionKey({
3513
+ * userWallet: '7xKNH....',
3514
+ * agentId: 'shopping-assistant-v1',
3515
+ * agentName: 'AI Shopping Assistant',
3516
+ * limitUSDC: 100,
3517
+ * durationDays: 7,
3518
+ * pin: '123456',
3519
+ * generateRecoveryQR: true,
3520
+ * });
3521
+ *
3522
+ * console.log('Session key created:', sessionKey.sessionKeyId);
3523
+ * console.log('Works across all apps with agent:', sessionKey.agentId);
3524
+ * console.log('Recovery QR:', sessionKey.recoveryQR);
3525
+ * ```
3077
3526
  */
3078
3527
  async createSessionKey(options) {
3079
3528
  const sessionKey = await DeviceBoundSessionKey.create({
@@ -3091,6 +3540,8 @@ var ZendFiSessionKeyManager = class {
3091
3540
  }
3092
3541
  const request = {
3093
3542
  userWallet: options.userWallet,
3543
+ agentId: options.agentId,
3544
+ agentName: options.agentName,
3094
3545
  limitUsdc: options.limitUSDC,
3095
3546
  durationDays: options.durationDays,
3096
3547
  encryptedSessionKey: encrypted.encryptedData,
@@ -3109,10 +3560,13 @@ var ZendFiSessionKeyManager = class {
3109
3560
  sessionKey.setSessionKeyId(response.sessionKeyId);
3110
3561
  return {
3111
3562
  sessionKeyId: response.sessionKeyId,
3563
+ agentId: response.agentId,
3564
+ agentName: response.agentName,
3112
3565
  sessionWallet: response.sessionWallet,
3113
3566
  expiresAt: response.expiresAt,
3114
3567
  recoveryQR,
3115
- limitUsdc: response.limitUsdc
3568
+ limitUsdc: response.limitUsdc,
3569
+ crossAppCompatible: response.crossAppCompatible
3116
3570
  };
3117
3571
  }
3118
3572
  /**
@@ -3667,30 +4121,2150 @@ function decodeSignatureFromLit(result) {
3667
4121
  }
3668
4122
  return bytes;
3669
4123
  }
4124
+
4125
+ // src/helpers/index.ts
4126
+ init_cache();
4127
+
4128
+ // src/helpers/ai.ts
4129
+ var PaymentIntentParser = class {
4130
+ /**
4131
+ * Parse natural language into structured intent
4132
+ */
4133
+ static parse(text) {
4134
+ if (!text || typeof text !== "string") return null;
4135
+ const lowerText = text.toLowerCase().trim();
4136
+ let action = "chat_only";
4137
+ let confidence = 0;
4138
+ if (this.containsPaymentKeywords(lowerText)) {
4139
+ action = "payment";
4140
+ confidence = 0.7;
4141
+ const amount = this.extractAmount(lowerText);
4142
+ const description = this.extractDescription(lowerText);
4143
+ if (amount && description) {
4144
+ confidence = 0.9;
4145
+ return { action, amount, description, confidence, rawText: text };
4146
+ }
4147
+ if (amount) {
4148
+ confidence = 0.8;
4149
+ return { action, amount, confidence, rawText: text };
4150
+ }
4151
+ }
4152
+ if (this.containsSessionKeywords(lowerText)) {
4153
+ action = "create_session";
4154
+ confidence = 0.8;
4155
+ const amount = this.extractAmount(lowerText);
4156
+ return { action, amount, confidence, rawText: text, description: "Session key budget" };
4157
+ }
4158
+ if (this.containsStatusKeywords(lowerText)) {
4159
+ action = lowerText.includes("session") || lowerText.includes("key") ? "check_status" : "check_balance";
4160
+ confidence = 0.9;
4161
+ return { action, confidence, rawText: text };
4162
+ }
4163
+ if (this.containsTopUpKeywords(lowerText)) {
4164
+ action = "topup";
4165
+ confidence = 0.8;
4166
+ const amount = this.extractAmount(lowerText);
4167
+ return { action, amount, confidence, rawText: text };
4168
+ }
4169
+ if (this.containsRevokeKeywords(lowerText)) {
4170
+ action = "revoke";
4171
+ confidence = 0.9;
4172
+ return { action, confidence, rawText: text };
4173
+ }
4174
+ if (this.containsAutonomyKeywords(lowerText)) {
4175
+ action = "enable_autonomy";
4176
+ confidence = 0.8;
4177
+ const amount = this.extractAmount(lowerText);
4178
+ return { action, amount, confidence, rawText: text, description: "Autonomous delegate limit" };
4179
+ }
4180
+ return null;
4181
+ }
4182
+ /**
4183
+ * Generate system prompt for AI models
4184
+ */
4185
+ static generateSystemPrompt(capabilities = {}) {
4186
+ const enabledFeatures = Object.entries({
4187
+ createPayment: capabilities.createPayment !== false,
4188
+ createSessionKey: capabilities.createSessionKey !== false,
4189
+ checkBalance: capabilities.checkBalance !== false,
4190
+ checkStatus: capabilities.checkStatus !== false,
4191
+ topUpSession: capabilities.topUpSession !== false,
4192
+ revokeSession: capabilities.revokeSession !== false,
4193
+ enableAutonomy: capabilities.enableAutonomy !== false
4194
+ }).filter(([_, enabled]) => enabled).map(([feature]) => feature);
4195
+ return `You are a ZendFi payment assistant. You can help users with crypto payments on Solana.
4196
+
4197
+ Available Actions:
4198
+ ${enabledFeatures.includes("createPayment") ? '- Make payments (e.g., "Buy coffee for $5", "Send $20 to merchant")' : ""}
4199
+ ${enabledFeatures.includes("createSessionKey") ? '- Create session keys (e.g., "Create a session key with $100 budget")' : ""}
4200
+ ${enabledFeatures.includes("checkBalance") ? `- Check balances (e.g., "What's my balance?", "How much do I have?")` : ""}
4201
+ ${enabledFeatures.includes("checkStatus") ? '- Check session status (e.g., "Session key status", "How much is left?")' : ""}
4202
+ ${enabledFeatures.includes("topUpSession") ? '- Top up session keys (e.g., "Add $50 to session key", "Top up with $100")' : ""}
4203
+ ${enabledFeatures.includes("revokeSession") ? '- Revoke session keys (e.g., "Revoke session key", "Cancel my session")' : ""}
4204
+ ${enabledFeatures.includes("enableAutonomy") ? '- Enable autonomous mode (e.g., "Enable auto-pay with $25 limit")' : ""}
4205
+
4206
+ Response Format:
4207
+ Always respond with valid JSON:
4208
+ {
4209
+ "action": "payment" | "create_session" | "check_balance" | "check_status" | "topup" | "revoke" | "enable_autonomy" | "chat_only",
4210
+ "amount_usd": <number if applicable>,
4211
+ "description": "<description>",
4212
+ "message": "<friendly response to user>"
4213
+ }
4214
+
4215
+ Examples:
4216
+ User: "Buy coffee for $5"
4217
+ You: {"action": "payment", "amount_usd": 5, "description": "coffee", "message": "Sure! Processing your $5 coffee payment..."}
4218
+
4219
+ User: "Create a session key with $100"
4220
+ You: {"action": "create_session", "amount_usd": 100, "message": "I'll create a session key with a $100 budget..."}
4221
+
4222
+ User: "What's my balance?"
4223
+ You: {"action": "check_balance", "message": "Let me check your balance..."}
4224
+
4225
+ Be helpful, concise, and always respond in valid JSON format.`;
4226
+ }
4227
+ // ============================================
4228
+ // Keyword Detection
4229
+ // ============================================
4230
+ static containsPaymentKeywords(text) {
4231
+ const keywords = [
4232
+ "buy",
4233
+ "purchase",
4234
+ "pay",
4235
+ "send",
4236
+ "transfer",
4237
+ "payment",
4238
+ "order",
4239
+ "checkout",
4240
+ "subscribe",
4241
+ "donate",
4242
+ "tip"
4243
+ ];
4244
+ return keywords.some((kw) => text.includes(kw));
4245
+ }
4246
+ static containsSessionKeywords(text) {
4247
+ const keywords = [
4248
+ "create session",
4249
+ "new session",
4250
+ "session key",
4251
+ "setup session",
4252
+ "generate session",
4253
+ "make session",
4254
+ "start session"
4255
+ ];
4256
+ return keywords.some((kw) => text.includes(kw));
4257
+ }
4258
+ static containsStatusKeywords(text) {
4259
+ const keywords = [
4260
+ "status",
4261
+ "balance",
4262
+ "remaining",
4263
+ "left",
4264
+ "how much",
4265
+ "check",
4266
+ "show",
4267
+ "view",
4268
+ "display"
4269
+ ];
4270
+ return keywords.some((kw) => text.includes(kw));
4271
+ }
4272
+ static containsTopUpKeywords(text) {
4273
+ const keywords = [
4274
+ "top up",
4275
+ "topup",
4276
+ "add funds",
4277
+ "add money",
4278
+ "fund",
4279
+ "increase",
4280
+ "reload",
4281
+ "refill"
4282
+ ];
4283
+ return keywords.some((kw) => text.includes(kw));
4284
+ }
4285
+ static containsRevokeKeywords(text) {
4286
+ const keywords = [
4287
+ "revoke",
4288
+ "cancel",
4289
+ "disable",
4290
+ "remove",
4291
+ "delete",
4292
+ "stop",
4293
+ "end",
4294
+ "terminate"
4295
+ ];
4296
+ return keywords.some((kw) => text.includes(kw));
4297
+ }
4298
+ static containsAutonomyKeywords(text) {
4299
+ const keywords = [
4300
+ "autonomous",
4301
+ "auto",
4302
+ "automatic",
4303
+ "delegate",
4304
+ "enable auto",
4305
+ "auto-sign",
4306
+ "auto sign",
4307
+ "auto-pay",
4308
+ "auto pay"
4309
+ ];
4310
+ return keywords.some((kw) => text.includes(kw));
4311
+ }
4312
+ // ============================================
4313
+ // Amount Extraction
4314
+ // ============================================
4315
+ static extractAmount(text) {
4316
+ const dollarMatch = text.match(/\$\s*(\d+(?:\.\d{1,2})?)/);
4317
+ if (dollarMatch) {
4318
+ return parseFloat(dollarMatch[1]);
4319
+ }
4320
+ const usdMatch = text.match(/(\d+(?:\.\d{1,2})?)\s*(?:usd|usdc|dollars?)/i);
4321
+ if (usdMatch) {
4322
+ return parseFloat(usdMatch[1]);
4323
+ }
4324
+ const numberMatch = text.match(/(\d+(?:\.\d{1,2})?)/);
4325
+ if (numberMatch) {
4326
+ const num = parseFloat(numberMatch[1]);
4327
+ if (num > 0 && num < 1e5) {
4328
+ return num;
4329
+ }
4330
+ }
4331
+ return void 0;
4332
+ }
4333
+ // ============================================
4334
+ // Description Extraction
4335
+ // ============================================
4336
+ static extractDescription(text) {
4337
+ const lowerText = text.toLowerCase();
4338
+ const items = {
4339
+ "coffee": ["coffee", "espresso", "latte", "cappuccino"],
4340
+ "food": ["food", "meal", "lunch", "dinner", "breakfast"],
4341
+ "drink": ["drink", "beverage", "soda", "juice"],
4342
+ "book": ["book", "ebook", "magazine"],
4343
+ "subscription": ["subscription", "membership", "plan"],
4344
+ "tip": ["tip", "gratuity"],
4345
+ "donation": ["donate", "donation", "contribute"],
4346
+ "game": ["game", "gaming"],
4347
+ "music": ["music", "song", "album"],
4348
+ "video": ["video", "movie", "film"]
4349
+ };
4350
+ for (const [item, keywords] of Object.entries(items)) {
4351
+ if (keywords.some((kw) => lowerText.includes(kw))) {
4352
+ return item;
4353
+ }
4354
+ }
4355
+ const forMatch = text.match(/for\s+(.+?)(?:\s+\$|\s+usd|$)/i);
4356
+ if (forMatch && forMatch[1]) {
4357
+ const desc = forMatch[1].trim();
4358
+ if (desc.length > 0 && desc.length < 50) {
4359
+ return desc;
4360
+ }
4361
+ }
4362
+ return void 0;
4363
+ }
4364
+ };
4365
+ var OpenAIAdapter = class {
4366
+ constructor(apiKey, model = "gpt-4o-mini", capabilities) {
4367
+ this.apiKey = apiKey;
4368
+ this.model = model;
4369
+ this.capabilities = capabilities;
4370
+ }
4371
+ async chat(message, conversationHistory = []) {
4372
+ const systemPrompt = PaymentIntentParser.generateSystemPrompt(this.capabilities);
4373
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
4374
+ method: "POST",
4375
+ headers: {
4376
+ "Authorization": `Bearer ${this.apiKey}`,
4377
+ "Content-Type": "application/json"
4378
+ },
4379
+ body: JSON.stringify({
4380
+ model: this.model,
4381
+ messages: [
4382
+ { role: "system", content: systemPrompt },
4383
+ ...conversationHistory,
4384
+ { role: "user", content: message }
4385
+ ],
4386
+ temperature: 0.7,
4387
+ max_tokens: 500
4388
+ })
4389
+ });
4390
+ if (!response.ok) {
4391
+ throw new Error(`OpenAI API error: ${response.status} ${response.statusText}`);
4392
+ }
4393
+ const data = await response.json();
4394
+ const text = data.choices[0]?.message?.content || "";
4395
+ let intent = null;
4396
+ try {
4397
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
4398
+ if (jsonMatch) {
4399
+ const parsed = JSON.parse(jsonMatch[0]);
4400
+ intent = {
4401
+ action: parsed.action || "chat_only",
4402
+ amount: parsed.amount_usd,
4403
+ description: parsed.description,
4404
+ confidence: 0.9,
4405
+ rawText: text,
4406
+ metadata: { message: parsed.message }
4407
+ };
4408
+ }
4409
+ } catch {
4410
+ intent = PaymentIntentParser.parse(text);
4411
+ }
4412
+ return { text, intent, raw: data };
4413
+ }
4414
+ };
4415
+ var AnthropicAdapter = class {
4416
+ constructor(apiKey, model = "claude-3-5-sonnet-20241022", capabilities) {
4417
+ this.apiKey = apiKey;
4418
+ this.model = model;
4419
+ this.capabilities = capabilities;
4420
+ }
4421
+ async chat(message, conversationHistory = []) {
4422
+ const systemPrompt = PaymentIntentParser.generateSystemPrompt(this.capabilities);
4423
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
4424
+ method: "POST",
4425
+ headers: {
4426
+ "x-api-key": this.apiKey,
4427
+ "anthropic-version": "2023-06-01",
4428
+ "Content-Type": "application/json"
4429
+ },
4430
+ body: JSON.stringify({
4431
+ model: this.model,
4432
+ system: systemPrompt,
4433
+ messages: [
4434
+ ...conversationHistory.map((msg) => ({
4435
+ role: msg.role === "user" ? "user" : "assistant",
4436
+ content: msg.content
4437
+ })),
4438
+ { role: "user", content: message }
4439
+ ],
4440
+ max_tokens: 500
4441
+ })
4442
+ });
4443
+ if (!response.ok) {
4444
+ throw new Error(`Anthropic API error: ${response.status} ${response.statusText}`);
4445
+ }
4446
+ const data = await response.json();
4447
+ const text = data.content[0]?.text || "";
4448
+ let intent = null;
4449
+ try {
4450
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
4451
+ if (jsonMatch) {
4452
+ const parsed = JSON.parse(jsonMatch[0]);
4453
+ intent = {
4454
+ action: parsed.action || "chat_only",
4455
+ amount: parsed.amount_usd,
4456
+ description: parsed.description,
4457
+ confidence: 0.9,
4458
+ rawText: text,
4459
+ metadata: { message: parsed.message }
4460
+ };
4461
+ }
4462
+ } catch {
4463
+ intent = PaymentIntentParser.parse(text);
4464
+ }
4465
+ return { text, intent, raw: data };
4466
+ }
4467
+ };
4468
+ var GeminiAdapter = class {
4469
+ constructor(apiKey, model = "gemini-2.0-flash-exp", capabilities) {
4470
+ this.apiKey = apiKey;
4471
+ this.model = model;
4472
+ this.capabilities = capabilities;
4473
+ }
4474
+ async chat(message) {
4475
+ const systemPrompt = PaymentIntentParser.generateSystemPrompt(this.capabilities);
4476
+ const fullPrompt = `${systemPrompt}
4477
+
4478
+ User: ${message}`;
4479
+ const response = await fetch(
4480
+ `https://generativelanguage.googleapis.com/v1beta/models/${this.model}:generateContent?key=${this.apiKey}`,
4481
+ {
4482
+ method: "POST",
4483
+ headers: { "Content-Type": "application/json" },
4484
+ body: JSON.stringify({
4485
+ contents: [{ parts: [{ text: fullPrompt }] }]
4486
+ })
4487
+ }
4488
+ );
4489
+ if (!response.ok) {
4490
+ throw new Error(`Gemini API error: ${response.status} ${response.statusText}`);
4491
+ }
4492
+ const data = await response.json();
4493
+ const text = data.candidates?.[0]?.content?.parts?.[0]?.text || "";
4494
+ let intent = null;
4495
+ try {
4496
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
4497
+ if (jsonMatch) {
4498
+ const parsed = JSON.parse(jsonMatch[0]);
4499
+ intent = {
4500
+ action: parsed.action || "chat_only",
4501
+ amount: parsed.amount_usd,
4502
+ description: parsed.description,
4503
+ confidence: 0.9,
4504
+ rawText: text,
4505
+ metadata: { message: parsed.message }
4506
+ };
4507
+ }
4508
+ } catch {
4509
+ intent = PaymentIntentParser.parse(text);
4510
+ }
4511
+ return { text, intent, raw: data };
4512
+ }
4513
+ };
4514
+
4515
+ // src/helpers/wallet.ts
4516
+ var WalletConnector = class _WalletConnector {
4517
+ static connectedWallet = null;
4518
+ /**
4519
+ * Detect and connect to a Solana wallet
4520
+ */
4521
+ static async detectAndConnect(config = {}) {
4522
+ if (this.connectedWallet && this.connectedWallet.isConnected()) {
4523
+ return this.connectedWallet;
4524
+ }
4525
+ const detected = this.detectWallets();
4526
+ if (detected.length === 0) {
4527
+ if (config.showInstallPrompt !== false) {
4528
+ this.showInstallPrompt();
4529
+ }
4530
+ throw new Error("No Solana wallet detected. Please install Phantom, Solflare, or Backpack.");
4531
+ }
4532
+ let selectedProvider = detected[0];
4533
+ if (config.preferredProvider && detected.includes(config.preferredProvider)) {
4534
+ selectedProvider = config.preferredProvider;
4535
+ }
4536
+ const wallet = await this.connectToProvider(selectedProvider);
4537
+ this.connectedWallet = wallet;
4538
+ return wallet;
4539
+ }
4540
+ /**
4541
+ * Detect available Solana wallets
4542
+ */
4543
+ static detectWallets() {
4544
+ const detected = [];
4545
+ if (typeof window === "undefined") return detected;
4546
+ if (window.solana?.isPhantom) detected.push("phantom");
4547
+ if (window.solflare?.isSolflare) detected.push("solflare");
4548
+ if (window.backpack?.isBackpack) detected.push("backpack");
4549
+ if (window.coinbaseSolana) detected.push("coinbase");
4550
+ if (window.trustwallet?.solana) detected.push("trust");
4551
+ return detected;
4552
+ }
4553
+ /**
4554
+ * Connect to a specific wallet provider
4555
+ */
4556
+ static async connectToProvider(provider) {
4557
+ if (typeof window === "undefined") {
4558
+ throw new Error("Wallet connection only works in browser environment");
4559
+ }
4560
+ let adapter;
4561
+ switch (provider) {
4562
+ case "phantom":
4563
+ adapter = window.solana;
4564
+ if (!adapter?.isPhantom) {
4565
+ throw new Error("Phantom wallet not found");
4566
+ }
4567
+ break;
4568
+ case "solflare":
4569
+ adapter = window.solflare;
4570
+ if (!adapter?.isSolflare) {
4571
+ throw new Error("Solflare wallet not found");
4572
+ }
4573
+ break;
4574
+ case "backpack":
4575
+ adapter = window.backpack;
4576
+ if (!adapter?.isBackpack) {
4577
+ throw new Error("Backpack wallet not found");
4578
+ }
4579
+ break;
4580
+ case "coinbase":
4581
+ adapter = window.coinbaseSolana;
4582
+ if (!adapter) {
4583
+ throw new Error("Coinbase Wallet not found");
4584
+ }
4585
+ break;
4586
+ case "trust":
4587
+ adapter = window.trustwallet?.solana;
4588
+ if (!adapter) {
4589
+ throw new Error("Trust Wallet not found");
4590
+ }
4591
+ break;
4592
+ default:
4593
+ throw new Error(`Unknown wallet provider: ${provider}`);
4594
+ }
4595
+ try {
4596
+ const response = await adapter.connect();
4597
+ const publicKey = response.publicKey || adapter.publicKey;
4598
+ if (!publicKey) {
4599
+ throw new Error("Failed to get wallet public key");
4600
+ }
4601
+ const connectedWallet = {
4602
+ address: publicKey.toString(),
4603
+ provider,
4604
+ publicKey,
4605
+ signTransaction: async (tx) => {
4606
+ return await adapter.signTransaction(tx);
4607
+ },
4608
+ signAllTransactions: async (txs) => {
4609
+ if (adapter.signAllTransactions) {
4610
+ return await adapter.signAllTransactions(txs);
4611
+ }
4612
+ const signed = [];
4613
+ for (const tx of txs) {
4614
+ signed.push(await adapter.signTransaction(tx));
4615
+ }
4616
+ return signed;
4617
+ },
4618
+ signMessage: async (message) => {
4619
+ if (adapter.signMessage) {
4620
+ return await adapter.signMessage(message);
4621
+ }
4622
+ throw new Error(`${provider} does not support message signing`);
4623
+ },
4624
+ disconnect: async () => {
4625
+ if (adapter.disconnect) {
4626
+ await adapter.disconnect();
4627
+ }
4628
+ _WalletConnector.connectedWallet = null;
4629
+ },
4630
+ isConnected: () => {
4631
+ return adapter.isConnected ?? false;
4632
+ },
4633
+ raw: adapter
4634
+ };
4635
+ return connectedWallet;
4636
+ } catch (error) {
4637
+ throw new Error(`Failed to connect to ${provider}: ${error.message}`);
4638
+ }
4639
+ }
4640
+ /**
4641
+ * Sign and submit a transaction
4642
+ */
4643
+ static async signAndSubmit(transaction, wallet, connection) {
4644
+ const signedTx = await wallet.signTransaction(transaction);
4645
+ const signature = await connection.sendRawTransaction(signedTx.serialize(), {
4646
+ skipPreflight: false,
4647
+ preflightCommitment: "confirmed"
4648
+ });
4649
+ return { signature };
4650
+ }
4651
+ /**
4652
+ * Get current connected wallet
4653
+ */
4654
+ static getConnectedWallet() {
4655
+ return this.connectedWallet;
4656
+ }
4657
+ /**
4658
+ * Disconnect current wallet
4659
+ */
4660
+ static async disconnect() {
4661
+ if (this.connectedWallet) {
4662
+ await this.connectedWallet.disconnect();
4663
+ this.connectedWallet = null;
4664
+ }
4665
+ }
4666
+ /**
4667
+ * Listen for wallet connection changes
4668
+ */
4669
+ static onAccountChange(callback) {
4670
+ if (typeof window === "undefined") {
4671
+ return () => {
4672
+ };
4673
+ }
4674
+ const cleanupFns = [];
4675
+ if (window.solana?.on) {
4676
+ window.solana.on("accountChanged", callback);
4677
+ cleanupFns.push(() => {
4678
+ window.solana?.removeListener("accountChanged", callback);
4679
+ });
4680
+ }
4681
+ if (window.solflare?.on) {
4682
+ window.solflare.on("accountChanged", callback);
4683
+ cleanupFns.push(() => {
4684
+ window.solflare?.removeListener("accountChanged", callback);
4685
+ });
4686
+ }
4687
+ return () => {
4688
+ cleanupFns.forEach((fn) => fn());
4689
+ };
4690
+ }
4691
+ /**
4692
+ * Listen for wallet disconnection
4693
+ */
4694
+ static onDisconnect(callback) {
4695
+ if (typeof window === "undefined") {
4696
+ return () => {
4697
+ };
4698
+ }
4699
+ const cleanupFns = [];
4700
+ if (window.solana?.on) {
4701
+ window.solana.on("disconnect", callback);
4702
+ cleanupFns.push(() => {
4703
+ window.solana?.removeListener("disconnect", callback);
4704
+ });
4705
+ }
4706
+ if (window.solflare?.on) {
4707
+ window.solflare.on("disconnect", callback);
4708
+ cleanupFns.push(() => {
4709
+ window.solflare?.removeListener("disconnect", callback);
4710
+ });
4711
+ }
4712
+ return () => {
4713
+ cleanupFns.forEach((fn) => fn());
4714
+ };
4715
+ }
4716
+ /**
4717
+ * Show install prompt UI
4718
+ */
4719
+ static showInstallPrompt() {
4720
+ const message = `
4721
+ No Solana wallet detected!
4722
+
4723
+ Install one of these wallets:
4724
+ \u2022 Phantom: https://phantom.app
4725
+ \u2022 Solflare: https://solflare.com
4726
+ \u2022 Backpack: https://backpack.app
4727
+ `.trim();
4728
+ console.warn(message);
4729
+ if (typeof window !== "undefined") {
4730
+ const userChoice = window.confirm(
4731
+ "No Solana wallet detected.\n\nWould you like to install Phantom wallet?"
4732
+ );
4733
+ if (userChoice) {
4734
+ window.open("https://phantom.app", "_blank");
4735
+ }
4736
+ }
4737
+ }
4738
+ /**
4739
+ * Check if wallet is installed
4740
+ */
4741
+ static isWalletInstalled(provider) {
4742
+ return this.detectWallets().includes(provider);
4743
+ }
4744
+ /**
4745
+ * Get wallet download URL
4746
+ */
4747
+ static getWalletUrl(provider) {
4748
+ const urls = {
4749
+ phantom: "https://phantom.app",
4750
+ solflare: "https://solflare.com",
4751
+ backpack: "https://backpack.app",
4752
+ coinbase: "https://www.coinbase.com/wallet",
4753
+ trust: "https://trustwallet.com"
4754
+ };
4755
+ return urls[provider];
4756
+ }
4757
+ };
4758
+ function createWalletHook() {
4759
+ let useState;
4760
+ let useEffect;
4761
+ try {
4762
+ const React = require("react");
4763
+ useState = React.useState;
4764
+ useEffect = React.useEffect;
4765
+ } catch {
4766
+ throw new Error("React not found. Install react to use wallet hooks.");
4767
+ }
4768
+ return function useWallet() {
4769
+ const [wallet, setWallet] = useState(null);
4770
+ const [connecting, setConnecting] = useState(false);
4771
+ const [error, setError] = useState(null);
4772
+ const connect = async (config) => {
4773
+ setConnecting(true);
4774
+ setError(null);
4775
+ try {
4776
+ const connected = await WalletConnector.detectAndConnect(config);
4777
+ setWallet(connected);
4778
+ } catch (err) {
4779
+ setError(err);
4780
+ } finally {
4781
+ setConnecting(false);
4782
+ }
4783
+ };
4784
+ const disconnect = async () => {
4785
+ await WalletConnector.disconnect();
4786
+ setWallet(null);
4787
+ };
4788
+ useEffect(() => {
4789
+ const cleanup = WalletConnector.onAccountChange((publicKey) => {
4790
+ if (wallet) {
4791
+ setWallet({ ...wallet, publicKey, address: publicKey.toString() });
4792
+ }
4793
+ });
4794
+ return cleanup;
4795
+ }, [wallet]);
4796
+ return {
4797
+ wallet,
4798
+ connecting,
4799
+ error,
4800
+ connect,
4801
+ disconnect,
4802
+ isConnected: wallet !== null
4803
+ };
4804
+ };
4805
+ }
4806
+
4807
+ // src/helpers/security.ts
4808
+ var PINValidator = class {
4809
+ /**
4810
+ * Validate PIN strength and format
4811
+ */
4812
+ static validate(pin) {
4813
+ const errors = [];
4814
+ const suggestions = [];
4815
+ if (!pin || pin.length < 4) {
4816
+ errors.push("PIN must be at least 4 digits");
4817
+ }
4818
+ if (pin.length > 12) {
4819
+ errors.push("PIN must be at most 12 digits");
4820
+ }
4821
+ if (!/^\d+$/.test(pin)) {
4822
+ errors.push("PIN must contain only digits");
4823
+ }
4824
+ if (this.isWeakPattern(pin)) {
4825
+ errors.push("PIN is too simple");
4826
+ suggestions.push("Avoid sequential numbers (1234) or repeated digits (1111)");
4827
+ }
4828
+ let strength = "strong";
4829
+ if (errors.length > 0) {
4830
+ strength = "weak";
4831
+ } else if (pin.length <= 4 || this.hasRepeatedDigits(pin)) {
4832
+ strength = "weak";
4833
+ suggestions.push("Use at least 6 digits for better security");
4834
+ } else if (pin.length <= 6) {
4835
+ strength = "medium";
4836
+ suggestions.push("Use 8+ digits for maximum security");
4837
+ }
4838
+ return {
4839
+ valid: errors.length === 0,
4840
+ strength,
4841
+ errors,
4842
+ suggestions
4843
+ };
4844
+ }
4845
+ /**
4846
+ * Check PIN strength (0-100 score)
4847
+ */
4848
+ static strengthScore(pin) {
4849
+ if (!pin) return 0;
4850
+ let score = 0;
4851
+ score += Math.min(pin.length * 10, 50);
4852
+ const uniqueDigits = new Set(pin).size;
4853
+ score += uniqueDigits * 5;
4854
+ if (!this.isSequential(pin)) {
4855
+ score += 20;
4856
+ }
4857
+ if (!this.hasRepeatedDigits(pin)) {
4858
+ score += 10;
4859
+ }
4860
+ return Math.min(score, 100);
4861
+ }
4862
+ /**
4863
+ * Generate a secure random PIN
4864
+ */
4865
+ static generateSecureRandom(length = 6) {
4866
+ if (length < 4 || length > 12) {
4867
+ throw new Error("PIN length must be between 4 and 12");
4868
+ }
4869
+ const array = new Uint32Array(length);
4870
+ crypto.getRandomValues(array);
4871
+ const pin = Array.from(array).map((num) => num % 10).join("");
4872
+ if (this.isWeakPattern(pin)) {
4873
+ return this.generateSecureRandom(length);
4874
+ }
4875
+ return pin;
4876
+ }
4877
+ /**
4878
+ * Check for weak patterns
4879
+ */
4880
+ static isWeakPattern(pin) {
4881
+ if (this.isSequential(pin)) return true;
4882
+ if (/^(\d)\1+$/.test(pin)) return true;
4883
+ const commonPatterns = [
4884
+ "1234",
4885
+ "4321",
4886
+ "1111",
4887
+ "2222",
4888
+ "3333",
4889
+ "4444",
4890
+ "5555",
4891
+ "6666",
4892
+ "7777",
4893
+ "8888",
4894
+ "9999",
4895
+ "0000",
4896
+ "1212",
4897
+ "2323",
4898
+ "0123",
4899
+ "3210",
4900
+ "9876",
4901
+ "6789"
4902
+ ];
4903
+ return commonPatterns.some((pattern) => pin.includes(pattern));
4904
+ }
4905
+ /**
4906
+ * Check if PIN is sequential
4907
+ */
4908
+ static isSequential(pin) {
4909
+ for (let i = 1; i < pin.length; i++) {
4910
+ const diff = parseInt(pin[i]) - parseInt(pin[i - 1]);
4911
+ if (Math.abs(diff) !== 1) return false;
4912
+ }
4913
+ return true;
4914
+ }
4915
+ /**
4916
+ * Check if PIN has repeated digits
4917
+ */
4918
+ static hasRepeatedDigits(pin) {
4919
+ return /(\d)\1{2,}/.test(pin);
4920
+ }
4921
+ /**
4922
+ * Hash PIN for storage (if needed)
4923
+ * Note: For device-bound keys, the PIN is used for key derivation, not storage
4924
+ */
4925
+ static async hashPIN(pin, salt) {
4926
+ const actualSalt = salt || crypto.getRandomValues(new Uint8Array(16));
4927
+ const encoder = new TextEncoder();
4928
+ const data = encoder.encode(pin + actualSalt);
4929
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
4930
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
4931
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
4932
+ }
4933
+ };
4934
+ var PINRateLimiter = class {
4935
+ attempts = /* @__PURE__ */ new Map();
4936
+ locked = /* @__PURE__ */ new Map();
4937
+ maxAttempts;
4938
+ windowMs;
4939
+ lockoutMs;
4940
+ constructor(config = {}) {
4941
+ this.maxAttempts = config.maxAttempts || 3;
4942
+ this.windowMs = config.windowMs || 6e4;
4943
+ this.lockoutMs = config.lockoutMs || 3e5;
4944
+ }
4945
+ /**
4946
+ * Check if attempt is allowed
4947
+ */
4948
+ async checkAttempt(sessionKeyId) {
4949
+ const now = Date.now();
4950
+ const lockoutUntil = this.locked.get(sessionKeyId);
4951
+ if (lockoutUntil && now < lockoutUntil) {
4952
+ const lockoutSeconds = Math.ceil((lockoutUntil - now) / 1e3);
4953
+ return {
4954
+ allowed: false,
4955
+ remainingAttempts: 0,
4956
+ lockoutSeconds
4957
+ };
4958
+ }
4959
+ if (lockoutUntil && now >= lockoutUntil) {
4960
+ this.locked.delete(sessionKeyId);
4961
+ this.attempts.delete(sessionKeyId);
4962
+ }
4963
+ const recentAttempts = this.getRecentAttempts(sessionKeyId, now);
4964
+ const remainingAttempts = this.maxAttempts - recentAttempts.length;
4965
+ if (remainingAttempts <= 0) {
4966
+ this.locked.set(sessionKeyId, now + this.lockoutMs);
4967
+ return {
4968
+ allowed: false,
4969
+ remainingAttempts: 0,
4970
+ lockoutSeconds: Math.ceil(this.lockoutMs / 1e3)
4971
+ };
4972
+ }
4973
+ return {
4974
+ allowed: true,
4975
+ remainingAttempts
4976
+ };
4977
+ }
4978
+ /**
4979
+ * Record a failed attempt
4980
+ */
4981
+ recordFailedAttempt(sessionKeyId) {
4982
+ const now = Date.now();
4983
+ const attempts = this.attempts.get(sessionKeyId) || [];
4984
+ attempts.push(now);
4985
+ this.attempts.set(sessionKeyId, attempts);
4986
+ }
4987
+ /**
4988
+ * Record a successful attempt (clears history)
4989
+ */
4990
+ recordSuccessfulAttempt(sessionKeyId) {
4991
+ this.attempts.delete(sessionKeyId);
4992
+ this.locked.delete(sessionKeyId);
4993
+ }
4994
+ /**
4995
+ * Get recent attempts within window
4996
+ */
4997
+ getRecentAttempts(sessionKeyId, now) {
4998
+ const attempts = this.attempts.get(sessionKeyId) || [];
4999
+ const cutoff = now - this.windowMs;
5000
+ const recent = attempts.filter((timestamp) => timestamp > cutoff);
5001
+ if (recent.length !== attempts.length) {
5002
+ this.attempts.set(sessionKeyId, recent);
5003
+ }
5004
+ return recent;
5005
+ }
5006
+ /**
5007
+ * Clear all rate limit data
5008
+ */
5009
+ clear() {
5010
+ this.attempts.clear();
5011
+ this.locked.clear();
5012
+ }
5013
+ /**
5014
+ * Check lockout status
5015
+ */
5016
+ isLockedOut(sessionKeyId) {
5017
+ const lockoutUntil = this.locked.get(sessionKeyId);
5018
+ return lockoutUntil ? Date.now() < lockoutUntil : false;
5019
+ }
5020
+ /**
5021
+ * Get remaining lockout time
5022
+ */
5023
+ getRemainingLockoutTime(sessionKeyId) {
5024
+ const lockoutUntil = this.locked.get(sessionKeyId);
5025
+ if (!lockoutUntil) return 0;
5026
+ return Math.max(0, lockoutUntil - Date.now());
5027
+ }
5028
+ };
5029
+ var SecureStorage = class {
5030
+ /**
5031
+ * Store data with encryption (basic)
5032
+ * For production, consider using Web Crypto API with user-derived keys
5033
+ */
5034
+ static async setEncrypted(key, value, secret) {
5035
+ const encrypted = await this.encrypt(value, secret);
5036
+ localStorage.setItem(key, JSON.stringify(encrypted));
5037
+ }
5038
+ /**
5039
+ * Retrieve encrypted data
5040
+ */
5041
+ static async getEncrypted(key, secret) {
5042
+ const stored = localStorage.getItem(key);
5043
+ if (!stored) return null;
5044
+ try {
5045
+ const encrypted = JSON.parse(stored);
5046
+ return await this.decrypt(encrypted, secret);
5047
+ } catch {
5048
+ return null;
5049
+ }
5050
+ }
5051
+ /**
5052
+ * Encrypt string with AES-GCM
5053
+ */
5054
+ static async encrypt(plaintext, secret) {
5055
+ const encoder = new TextEncoder();
5056
+ const data = encoder.encode(plaintext);
5057
+ const key = await this.deriveKey(secret);
5058
+ const iv = crypto.getRandomValues(new Uint8Array(12));
5059
+ const encrypted = await crypto.subtle.encrypt(
5060
+ { name: "AES-GCM", iv },
5061
+ key,
5062
+ data
5063
+ );
5064
+ return {
5065
+ ciphertext: btoa(String.fromCharCode(...new Uint8Array(encrypted))),
5066
+ iv: btoa(String.fromCharCode(...iv))
5067
+ };
5068
+ }
5069
+ /**
5070
+ * Decrypt AES-GCM ciphertext
5071
+ */
5072
+ static async decrypt(encrypted, secret) {
5073
+ const key = await this.deriveKey(secret);
5074
+ const iv = Uint8Array.from(atob(encrypted.iv), (c) => c.charCodeAt(0));
5075
+ const ciphertext = Uint8Array.from(atob(encrypted.ciphertext), (c) => c.charCodeAt(0));
5076
+ const decrypted = await crypto.subtle.decrypt(
5077
+ { name: "AES-GCM", iv },
5078
+ key,
5079
+ ciphertext
5080
+ );
5081
+ const decoder = new TextDecoder();
5082
+ return decoder.decode(decrypted);
5083
+ }
5084
+ /**
5085
+ * Derive encryption key from secret
5086
+ */
5087
+ static async deriveKey(secret) {
5088
+ const encoder = new TextEncoder();
5089
+ const keyMaterial = await crypto.subtle.importKey(
5090
+ "raw",
5091
+ encoder.encode(secret),
5092
+ { name: "PBKDF2" },
5093
+ false,
5094
+ ["deriveBits", "deriveKey"]
5095
+ );
5096
+ return await crypto.subtle.deriveKey(
5097
+ {
5098
+ name: "PBKDF2",
5099
+ salt: encoder.encode("zendfi-secure-storage"),
5100
+ iterations: 1e5,
5101
+ hash: "SHA-256"
5102
+ },
5103
+ keyMaterial,
5104
+ { name: "AES-GCM", length: 256 },
5105
+ false,
5106
+ ["encrypt", "decrypt"]
5107
+ );
5108
+ }
5109
+ /**
5110
+ * Clear all secure storage
5111
+ */
5112
+ static clearAll(namespace = "zendfi") {
5113
+ const keysToRemove = [];
5114
+ for (let i = 0; i < localStorage.length; i++) {
5115
+ const key = localStorage.key(i);
5116
+ if (key?.startsWith(namespace)) {
5117
+ keysToRemove.push(key);
5118
+ }
5119
+ }
5120
+ keysToRemove.forEach((key) => localStorage.removeItem(key));
5121
+ }
5122
+ };
5123
+
5124
+ // src/helpers/polling.ts
5125
+ var TransactionPoller = class {
5126
+ /**
5127
+ * Wait for transaction confirmation
5128
+ */
5129
+ static async waitForConfirmation(signature, options = {}) {
5130
+ const {
5131
+ timeout = 6e4,
5132
+ interval = 2e3,
5133
+ maxInterval = 1e4,
5134
+ maxAttempts = 30,
5135
+ commitment = "confirmed",
5136
+ rpcUrl
5137
+ } = options;
5138
+ const startTime = Date.now();
5139
+ let currentInterval = interval;
5140
+ let attempts = 0;
5141
+ while (true) {
5142
+ attempts++;
5143
+ if (Date.now() - startTime > timeout) {
5144
+ return {
5145
+ confirmed: false,
5146
+ signature,
5147
+ error: `Transaction confirmation timeout after ${timeout}ms`
5148
+ };
5149
+ }
5150
+ if (attempts > maxAttempts) {
5151
+ return {
5152
+ confirmed: false,
5153
+ signature,
5154
+ error: `Maximum polling attempts (${maxAttempts}) exceeded`
5155
+ };
5156
+ }
5157
+ try {
5158
+ const status = await this.checkTransactionStatus(signature, commitment, rpcUrl);
5159
+ if (status.confirmed) {
5160
+ return status;
5161
+ }
5162
+ if (status.error) {
5163
+ return status;
5164
+ }
5165
+ await this.sleep(currentInterval);
5166
+ currentInterval = Math.min(currentInterval * 1.5, maxInterval);
5167
+ } catch (error) {
5168
+ await this.sleep(currentInterval);
5169
+ currentInterval = Math.min(currentInterval * 1.5, maxInterval);
5170
+ }
5171
+ }
5172
+ }
5173
+ /**
5174
+ * Check transaction status via RPC
5175
+ */
5176
+ static async checkTransactionStatus(signature, commitment = "confirmed", rpcUrl) {
5177
+ const endpoint = rpcUrl || this.getDefaultRpcUrl();
5178
+ const response = await fetch(endpoint, {
5179
+ method: "POST",
5180
+ headers: { "Content-Type": "application/json" },
5181
+ body: JSON.stringify({
5182
+ jsonrpc: "2.0",
5183
+ id: 1,
5184
+ method: "getSignatureStatuses",
5185
+ params: [[signature], { searchTransactionHistory: true }]
5186
+ })
5187
+ });
5188
+ if (!response.ok) {
5189
+ throw new Error(`RPC error: ${response.status} ${response.statusText}`);
5190
+ }
5191
+ const data = await response.json();
5192
+ if (data.error) {
5193
+ return {
5194
+ confirmed: false,
5195
+ signature,
5196
+ error: data.error.message || "RPC error"
5197
+ };
5198
+ }
5199
+ const status = data.result?.value?.[0];
5200
+ if (!status) {
5201
+ return {
5202
+ confirmed: false,
5203
+ signature
5204
+ };
5205
+ }
5206
+ const isConfirmed = this.isCommitmentReached(status, commitment);
5207
+ return {
5208
+ confirmed: isConfirmed,
5209
+ signature,
5210
+ slot: status.slot,
5211
+ confirmations: status.confirmations,
5212
+ error: status.err ? JSON.stringify(status.err) : void 0
5213
+ };
5214
+ }
5215
+ /**
5216
+ * Check if commitment level is reached
5217
+ */
5218
+ static isCommitmentReached(status, commitment) {
5219
+ if (status.err) return false;
5220
+ switch (commitment) {
5221
+ case "processed":
5222
+ return true;
5223
+ // Any status means processed
5224
+ case "confirmed":
5225
+ return status.confirmationStatus === "confirmed" || status.confirmationStatus === "finalized";
5226
+ case "finalized":
5227
+ return status.confirmationStatus === "finalized";
5228
+ default:
5229
+ return false;
5230
+ }
5231
+ }
5232
+ /**
5233
+ * Get default RPC URL based on environment
5234
+ */
5235
+ static getDefaultRpcUrl() {
5236
+ if (typeof window !== "undefined" && window.location) {
5237
+ const hostname = window.location.hostname;
5238
+ if (hostname.includes("localhost") || hostname.includes("dev")) {
5239
+ return "https://api.devnet.solana.com";
5240
+ }
5241
+ }
5242
+ return "https://api.mainnet-beta.solana.com";
5243
+ }
5244
+ /**
5245
+ * Poll multiple transactions in parallel
5246
+ */
5247
+ static async waitForMultiple(signatures, options = {}) {
5248
+ return await Promise.all(
5249
+ signatures.map((sig) => this.waitForConfirmation(sig, options))
5250
+ );
5251
+ }
5252
+ /**
5253
+ * Get transaction details after confirmation
5254
+ */
5255
+ static async getTransactionDetails(signature, rpcUrl) {
5256
+ const endpoint = rpcUrl || this.getDefaultRpcUrl();
5257
+ const response = await fetch(endpoint, {
5258
+ method: "POST",
5259
+ headers: { "Content-Type": "application/json" },
5260
+ body: JSON.stringify({
5261
+ jsonrpc: "2.0",
5262
+ id: 1,
5263
+ method: "getTransaction",
5264
+ params: [
5265
+ signature,
5266
+ {
5267
+ encoding: "jsonParsed",
5268
+ commitment: "confirmed",
5269
+ maxSupportedTransactionVersion: 0
5270
+ }
5271
+ ]
5272
+ })
5273
+ });
5274
+ if (!response.ok) {
5275
+ throw new Error(`RPC error: ${response.status}`);
5276
+ }
5277
+ const data = await response.json();
5278
+ if (data.error) {
5279
+ throw new Error(data.error.message || "Failed to get transaction");
5280
+ }
5281
+ return data.result;
5282
+ }
5283
+ /**
5284
+ * Check if transaction exists on chain
5285
+ */
5286
+ static async exists(signature, rpcUrl) {
5287
+ try {
5288
+ const status = await this.checkTransactionStatus(signature, "confirmed", rpcUrl);
5289
+ return status.confirmed || !!status.slot;
5290
+ } catch {
5291
+ return false;
5292
+ }
5293
+ }
5294
+ /**
5295
+ * Get recent blockhash (useful for transaction building)
5296
+ */
5297
+ static async getRecentBlockhash(rpcUrl) {
5298
+ const endpoint = rpcUrl || this.getDefaultRpcUrl();
5299
+ const response = await fetch(endpoint, {
5300
+ method: "POST",
5301
+ headers: { "Content-Type": "application/json" },
5302
+ body: JSON.stringify({
5303
+ jsonrpc: "2.0",
5304
+ id: 1,
5305
+ method: "getLatestBlockhash",
5306
+ params: [{ commitment: "finalized" }]
5307
+ })
5308
+ });
5309
+ if (!response.ok) {
5310
+ throw new Error(`RPC error: ${response.status}`);
5311
+ }
5312
+ const data = await response.json();
5313
+ if (data.error) {
5314
+ throw new Error(data.error.message);
5315
+ }
5316
+ return data.result.value;
5317
+ }
5318
+ /**
5319
+ * Sleep utility
5320
+ */
5321
+ static sleep(ms) {
5322
+ return new Promise((resolve) => setTimeout(resolve, ms));
5323
+ }
5324
+ };
5325
+ var TransactionMonitor = class {
5326
+ monitors = /* @__PURE__ */ new Map();
5327
+ /**
5328
+ * Start monitoring a transaction
5329
+ */
5330
+ monitor(signature, callbacks, options = {}) {
5331
+ this.stopMonitoring(signature);
5332
+ const { timeout = 6e4, interval = 2e3 } = options;
5333
+ const startTime = Date.now();
5334
+ const intervalId = setInterval(async () => {
5335
+ if (Date.now() - startTime > timeout) {
5336
+ this.stopMonitoring(signature);
5337
+ callbacks.onTimeout?.();
5338
+ return;
5339
+ }
5340
+ try {
5341
+ const status = await TransactionPoller.waitForConfirmation(signature, {
5342
+ ...options,
5343
+ maxAttempts: 1
5344
+ // Single check per interval
5345
+ });
5346
+ if (status.confirmed) {
5347
+ this.stopMonitoring(signature);
5348
+ callbacks.onConfirmed?.(status);
5349
+ } else if (status.error) {
5350
+ this.stopMonitoring(signature);
5351
+ callbacks.onFailed?.(status);
5352
+ }
5353
+ } catch (error) {
5354
+ }
5355
+ }, interval);
5356
+ this.monitors.set(signature, { interval: intervalId, callbacks });
5357
+ }
5358
+ /**
5359
+ * Stop monitoring a transaction
5360
+ */
5361
+ stopMonitoring(signature) {
5362
+ const monitor = this.monitors.get(signature);
5363
+ if (monitor) {
5364
+ clearInterval(monitor.interval);
5365
+ this.monitors.delete(signature);
5366
+ }
5367
+ }
5368
+ /**
5369
+ * Stop all monitors
5370
+ */
5371
+ stopAll() {
5372
+ for (const [signature] of this.monitors) {
5373
+ this.stopMonitoring(signature);
5374
+ }
5375
+ }
5376
+ /**
5377
+ * Get active monitors
5378
+ */
5379
+ getActiveMonitors() {
5380
+ return Array.from(this.monitors.keys());
5381
+ }
5382
+ };
5383
+
5384
+ // src/helpers/recovery.ts
5385
+ var RetryStrategy = class {
5386
+ /**
5387
+ * Execute function with retry logic
5388
+ */
5389
+ static async withRetry(fn, options = {}) {
5390
+ const {
5391
+ maxAttempts = 3,
5392
+ backoffMs = 1e3,
5393
+ backoffMultiplier = 2,
5394
+ maxBackoffMs = 3e4,
5395
+ shouldRetry = () => true,
5396
+ onRetry
5397
+ } = options;
5398
+ let lastError = null;
5399
+ let currentBackoff = backoffMs;
5400
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
5401
+ try {
5402
+ return await fn();
5403
+ } catch (error) {
5404
+ lastError = error;
5405
+ if (attempt === maxAttempts || !shouldRetry(error, attempt)) {
5406
+ throw error;
5407
+ }
5408
+ const jitter = Math.random() * 0.3 * currentBackoff;
5409
+ const nextDelay = Math.min(currentBackoff + jitter, maxBackoffMs);
5410
+ onRetry?.(error, attempt, nextDelay);
5411
+ await this.sleep(nextDelay);
5412
+ currentBackoff *= backoffMultiplier;
5413
+ }
5414
+ }
5415
+ throw lastError || new Error("Retry failed");
5416
+ }
5417
+ /**
5418
+ * Retry with linear backoff
5419
+ */
5420
+ static async withLinearRetry(fn, maxAttempts = 3, delayMs = 1e3) {
5421
+ return this.withRetry(fn, {
5422
+ maxAttempts,
5423
+ backoffMs: delayMs,
5424
+ backoffMultiplier: 1
5425
+ // Linear
5426
+ });
5427
+ }
5428
+ /**
5429
+ * Retry with custom backoff function
5430
+ */
5431
+ static async withCustomBackoff(fn, backoffFn, maxAttempts = 3) {
5432
+ let lastError = null;
5433
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
5434
+ try {
5435
+ return await fn();
5436
+ } catch (error) {
5437
+ lastError = error;
5438
+ if (attempt === maxAttempts) {
5439
+ throw error;
5440
+ }
5441
+ const delay = backoffFn(attempt);
5442
+ await this.sleep(delay);
5443
+ }
5444
+ }
5445
+ throw lastError || new Error("Retry failed");
5446
+ }
5447
+ /**
5448
+ * Check if error is retryable
5449
+ */
5450
+ static isRetryableError(error) {
5451
+ if (error.name === "NetworkError" || error.message?.includes("network")) {
5452
+ return true;
5453
+ }
5454
+ if (error.name === "TimeoutError" || error.message?.includes("timeout")) {
5455
+ return true;
5456
+ }
5457
+ if (error.status === 429 || error.code === "RATE_LIMIT_EXCEEDED") {
5458
+ return true;
5459
+ }
5460
+ if (error.status >= 500 && error.status < 600) {
5461
+ return true;
5462
+ }
5463
+ if (error.message?.includes("blockhash") || error.message?.includes("recent")) {
5464
+ return true;
5465
+ }
5466
+ return false;
5467
+ }
5468
+ static sleep(ms) {
5469
+ return new Promise((resolve) => setTimeout(resolve, ms));
5470
+ }
5471
+ };
5472
+ var ErrorRecovery = class {
5473
+ /**
5474
+ * Recover from network errors with retry
5475
+ */
5476
+ static async recoverFromNetworkError(fn, maxAttempts = 3) {
5477
+ return RetryStrategy.withRetry(fn, {
5478
+ maxAttempts,
5479
+ backoffMs: 2e3,
5480
+ shouldRetry: (error) => {
5481
+ return error.name === "NetworkError" || error.message?.includes("network") || error.message?.includes("fetch");
5482
+ },
5483
+ onRetry: (error, attempt, nextDelay) => {
5484
+ console.warn(
5485
+ `Network error (attempt ${attempt}), retrying in ${nextDelay}ms:`,
5486
+ error.message
5487
+ );
5488
+ }
5489
+ });
5490
+ }
5491
+ /**
5492
+ * Recover from rate limit errors with exponential backoff
5493
+ */
5494
+ static async recoverFromRateLimit(fn, maxAttempts = 5) {
5495
+ return RetryStrategy.withRetry(fn, {
5496
+ maxAttempts,
5497
+ backoffMs: 5e3,
5498
+ // Start with 5 seconds
5499
+ backoffMultiplier: 2,
5500
+ maxBackoffMs: 6e4,
5501
+ // Cap at 1 minute
5502
+ shouldRetry: (error) => {
5503
+ return error.status === 429 || error.code === "RATE_LIMIT_EXCEEDED";
5504
+ },
5505
+ onRetry: (attempt, nextDelay) => {
5506
+ console.warn(
5507
+ `Rate limited (attempt ${attempt}), waiting ${nextDelay}ms before retry`
5508
+ );
5509
+ }
5510
+ });
5511
+ }
5512
+ /**
5513
+ * Recover from Solana RPC errors (blockhash, etc.)
5514
+ */
5515
+ static async recoverFromRPCError(fn, maxAttempts = 3) {
5516
+ return RetryStrategy.withRetry(fn, {
5517
+ maxAttempts,
5518
+ backoffMs: 1e3,
5519
+ shouldRetry: (error) => {
5520
+ const message = error.message?.toLowerCase() || "";
5521
+ return message.includes("blockhash") || message.includes("recent") || message.includes("slot") || message.includes("rpc");
5522
+ },
5523
+ onRetry: (error, attempt, nextDelay) => {
5524
+ console.warn(
5525
+ `RPC error (attempt ${attempt}), retrying in ${nextDelay}ms:`,
5526
+ error.message
5527
+ );
5528
+ }
5529
+ });
5530
+ }
5531
+ /**
5532
+ * Recover from timeout errors
5533
+ */
5534
+ static async recoverFromTimeout(fn, timeoutMs = 3e4, maxAttempts = 2) {
5535
+ return RetryStrategy.withRetry(
5536
+ () => this.withTimeout(fn, timeoutMs),
5537
+ {
5538
+ maxAttempts,
5539
+ backoffMs: 5e3,
5540
+ shouldRetry: (error) => {
5541
+ return error.name === "TimeoutError" || error.message?.includes("timeout");
5542
+ }
5543
+ }
5544
+ );
5545
+ }
5546
+ /**
5547
+ * Add timeout to async function
5548
+ */
5549
+ static async withTimeout(fn, timeoutMs) {
5550
+ return Promise.race([
5551
+ fn(),
5552
+ new Promise((_, reject) => {
5553
+ setTimeout(() => {
5554
+ reject(new Error(`Operation timed out after ${timeoutMs}ms`));
5555
+ }, timeoutMs);
5556
+ })
5557
+ ]);
5558
+ }
5559
+ /**
5560
+ * Circuit breaker pattern for repeated failures
5561
+ */
5562
+ static createCircuitBreaker(fn, options = {}) {
5563
+ const {
5564
+ failureThreshold = 5,
5565
+ resetTimeoutMs = 6e4,
5566
+ onStateChange
5567
+ } = options;
5568
+ let state = "closed";
5569
+ let failureCount = 0;
5570
+ let lastFailureTime = 0;
5571
+ let resetTimer = null;
5572
+ return async () => {
5573
+ if (state === "open" && Date.now() - lastFailureTime > resetTimeoutMs) {
5574
+ state = "half-open";
5575
+ onStateChange?.("half-open");
5576
+ }
5577
+ if (state === "open") {
5578
+ throw new Error("Circuit breaker is OPEN - too many failures");
5579
+ }
5580
+ try {
5581
+ const result = await fn();
5582
+ if (state === "half-open") {
5583
+ state = "closed";
5584
+ onStateChange?.("closed");
5585
+ }
5586
+ failureCount = 0;
5587
+ return result;
5588
+ } catch (error) {
5589
+ failureCount++;
5590
+ lastFailureTime = Date.now();
5591
+ if (failureCount >= failureThreshold) {
5592
+ state = "open";
5593
+ onStateChange?.("open");
5594
+ if (resetTimer) clearTimeout(resetTimer);
5595
+ resetTimer = setTimeout(() => {
5596
+ state = "half-open";
5597
+ onStateChange?.("half-open");
5598
+ }, resetTimeoutMs);
5599
+ }
5600
+ throw error;
5601
+ }
5602
+ };
5603
+ }
5604
+ /**
5605
+ * Fallback to alternative function on error
5606
+ */
5607
+ static async withFallback(primaryFn, fallbackFn, shouldFallback = () => true) {
5608
+ try {
5609
+ return await primaryFn();
5610
+ } catch (error) {
5611
+ if (shouldFallback(error)) {
5612
+ console.warn("Primary function failed, using fallback:", error.message);
5613
+ return await fallbackFn();
5614
+ }
5615
+ throw error;
5616
+ }
5617
+ }
5618
+ /**
5619
+ * Graceful degradation - return partial result on error
5620
+ */
5621
+ static async withGracefulDegradation(fn, defaultValue, onError) {
5622
+ try {
5623
+ return await fn();
5624
+ } catch (error) {
5625
+ onError?.(error);
5626
+ console.warn("Operation failed, returning default value:", error.message);
5627
+ return defaultValue;
5628
+ }
5629
+ }
5630
+ };
5631
+
5632
+ // src/helpers/lifecycle.ts
5633
+ var SessionKeyLifecycle = class {
5634
+ constructor(client, config = {}) {
5635
+ this.client = client;
5636
+ this.config = config;
5637
+ if (config.autoCleanup && typeof window !== "undefined") {
5638
+ window.addEventListener("beforeunload", () => {
5639
+ this.cleanup().catch(console.error);
5640
+ });
5641
+ }
5642
+ }
5643
+ sessionKeyId = null;
5644
+ sessionWallet = null;
5645
+ encryptedKey = null;
5646
+ deviceFingerprint = null;
5647
+ /**
5648
+ * Create and fund session key in one call
5649
+ * Handles: keypair generation → encryption → backend registration → funding
5650
+ */
5651
+ async createAndFund(config) {
5652
+ const fingerprint = this.config.deviceFingerprintProvider ? await this.config.deviceFingerprintProvider() : await this.generateDeviceFingerprint();
5653
+ this.deviceFingerprint = fingerprint;
5654
+ const pin = this.config.pinProvider ? await this.config.pinProvider() : await this.promptForPIN("Create PIN for session key");
5655
+ const { Keypair: Keypair2 } = await this.getSolanaWeb3();
5656
+ const { SessionKeyCrypto: SessionKeyCrypto2 } = await Promise.resolve().then(() => (init_device_bound_crypto(), device_bound_crypto_exports));
5657
+ const keypair = Keypair2.generate();
5658
+ const encrypted = await SessionKeyCrypto2.encrypt(
5659
+ keypair.secretKey,
5660
+ pin,
5661
+ fingerprint
5662
+ );
5663
+ this.encryptedKey = {
5664
+ ciphertext: encrypted.encryptedData,
5665
+ nonce: encrypted.nonce
5666
+ };
5667
+ const response = await this.client.sessionKeys.createDeviceBound({
5668
+ user_wallet: config.userWallet,
5669
+ agent_id: config.agentId,
5670
+ agent_name: config.agentName,
5671
+ limit_usdc: config.limitUsdc,
5672
+ duration_days: config.durationDays || 7,
5673
+ encrypted_session_key: encrypted.encryptedData,
5674
+ nonce: encrypted.nonce,
5675
+ session_public_key: keypair.publicKey.toBase58(),
5676
+ device_fingerprint: fingerprint
5677
+ });
5678
+ this.sessionKeyId = response.session_key_id;
5679
+ this.sessionWallet = response.session_wallet;
5680
+ if (this.config.cache) {
5681
+ await this.config.cache.getCached(
5682
+ this.sessionKeyId,
5683
+ async () => keypair,
5684
+ { deviceFingerprint: fingerprint }
5685
+ );
5686
+ }
5687
+ if (config.onApprovalNeeded) {
5688
+ const topupResponse = await this.client.sessionKeys.topUp(
5689
+ this.sessionKeyId,
5690
+ {
5691
+ user_wallet: config.userWallet,
5692
+ amount_usdc: config.limitUsdc,
5693
+ device_fingerprint: fingerprint
5694
+ }
5695
+ );
5696
+ await config.onApprovalNeeded(topupResponse.top_up_transaction);
5697
+ }
5698
+ return {
5699
+ sessionKeyId: this.sessionKeyId,
5700
+ sessionWallet: this.sessionWallet
5701
+ };
5702
+ }
5703
+ /**
5704
+ * Make a payment using the session key
5705
+ * Auto-handles caching, PIN prompts, and signing
5706
+ */
5707
+ async pay(amount, description) {
5708
+ if (!this.sessionKeyId) {
5709
+ throw new Error("No active session key. Call createAndFund() first.");
5710
+ }
5711
+ let keypair;
5712
+ if (this.config.cache) {
5713
+ keypair = await this.config.cache.getCached(
5714
+ this.sessionKeyId,
5715
+ async () => {
5716
+ const pin = this.config.pinProvider ? await this.config.pinProvider() : await this.promptForPIN("Enter PIN to sign payment");
5717
+ return await this.decryptKeypair(pin);
5718
+ },
5719
+ { deviceFingerprint: this.deviceFingerprint || void 0 }
5720
+ );
5721
+ } else {
5722
+ const pin = this.config.pinProvider ? await this.config.pinProvider() : await this.promptForPIN("Enter PIN to sign payment");
5723
+ keypair = await this.decryptKeypair(pin);
5724
+ }
5725
+ const paymentResponse = await this.client.smart.execute({
5726
+ user_wallet: this.sessionWallet,
5727
+ // Session wallet, not user wallet
5728
+ amount_usd: amount,
5729
+ description,
5730
+ agent_id: "session-lifecycle",
5731
+ auto_detect_gasless: true
5732
+ });
5733
+ if (paymentResponse.requires_signature && paymentResponse.unsigned_transaction) {
5734
+ const { Transaction: Transaction3 } = await this.getSolanaWeb3();
5735
+ const txBuffer = Uint8Array.from(atob(paymentResponse.unsigned_transaction), (c) => c.charCodeAt(0));
5736
+ const tx = Transaction3.from(txBuffer);
5737
+ tx.partialSign(keypair);
5738
+ const submitUrl = paymentResponse.submit_url || `/api/v1/ai/payments/${paymentResponse.payment_id}/submit-signed`;
5739
+ const submitResponse = await fetch(`${this.client["config"].baseURL}${submitUrl}`, {
5740
+ method: "POST",
5741
+ headers: {
5742
+ "Authorization": `Bearer ${this.client["config"].apiKey}`,
5743
+ "Content-Type": "application/json"
5744
+ },
5745
+ body: JSON.stringify({
5746
+ signed_transaction: btoa(String.fromCharCode(...tx.serialize()))
5747
+ })
5748
+ });
5749
+ if (!submitResponse.ok) {
5750
+ throw new Error(`Failed to submit signed transaction: ${submitResponse.statusText}`);
5751
+ }
5752
+ const submitData = await submitResponse.json();
5753
+ return {
5754
+ paymentId: paymentResponse.payment_id,
5755
+ status: submitData.status,
5756
+ signature: submitData.transaction_signature,
5757
+ confirmedInMs: submitData.confirmed_in_ms
5758
+ };
5759
+ }
5760
+ return {
5761
+ paymentId: paymentResponse.payment_id,
5762
+ status: paymentResponse.status,
5763
+ signature: paymentResponse.transaction_signature,
5764
+ confirmedInMs: paymentResponse.confirmed_in_ms
5765
+ };
5766
+ }
5767
+ /**
5768
+ * Check session key status
5769
+ */
5770
+ async getStatus() {
5771
+ if (!this.sessionKeyId) {
5772
+ throw new Error("No active session key");
5773
+ }
5774
+ return await this.client.sessionKeys.getStatus(this.sessionKeyId);
5775
+ }
5776
+ /**
5777
+ * Top up session key
5778
+ */
5779
+ async topUp(amount, userWallet, onApprovalNeeded) {
5780
+ if (!this.sessionKeyId || !this.deviceFingerprint) {
5781
+ throw new Error("No active session key");
5782
+ }
5783
+ const response = await this.client.sessionKeys.topUp(
5784
+ this.sessionKeyId,
5785
+ {
5786
+ user_wallet: userWallet,
5787
+ amount_usdc: amount,
5788
+ device_fingerprint: this.deviceFingerprint
5789
+ }
5790
+ );
5791
+ if (onApprovalNeeded) {
5792
+ await onApprovalNeeded(response.top_up_transaction);
5793
+ }
5794
+ }
5795
+ /**
5796
+ * Revoke session key
5797
+ */
5798
+ async revoke() {
5799
+ if (!this.sessionKeyId) {
5800
+ throw new Error("No active session key");
5801
+ }
5802
+ await this.client.sessionKeys.revoke(this.sessionKeyId);
5803
+ await this.cleanup();
5804
+ }
5805
+ /**
5806
+ * Cleanup (clear cache, reset state)
5807
+ */
5808
+ async cleanup() {
5809
+ if (this.config.cache && this.sessionKeyId) {
5810
+ await this.config.cache.invalidate(this.sessionKeyId);
5811
+ }
5812
+ this.sessionKeyId = null;
5813
+ this.sessionWallet = null;
5814
+ this.encryptedKey = null;
5815
+ this.deviceFingerprint = null;
5816
+ }
5817
+ /**
5818
+ * Get current session key ID
5819
+ */
5820
+ getSessionKeyId() {
5821
+ return this.sessionKeyId;
5822
+ }
5823
+ /**
5824
+ * Check if session is active
5825
+ */
5826
+ isActive() {
5827
+ return this.sessionKeyId !== null;
5828
+ }
5829
+ // ============================================
5830
+ // Private Helpers
5831
+ // ============================================
5832
+ async decryptKeypair(pin) {
5833
+ if (!this.encryptedKey || !this.deviceFingerprint) {
5834
+ throw new Error("No encrypted key available");
5835
+ }
5836
+ const { SessionKeyCrypto: SessionKeyCrypto2 } = await Promise.resolve().then(() => (init_device_bound_crypto(), device_bound_crypto_exports));
5837
+ const encrypted = {
5838
+ encryptedData: this.encryptedKey.ciphertext,
5839
+ nonce: this.encryptedKey.nonce,
5840
+ publicKey: "",
5841
+ // Not needed for decryption
5842
+ deviceFingerprint: this.deviceFingerprint,
5843
+ version: "argon2id-aes256gcm-v1"
5844
+ };
5845
+ return await SessionKeyCrypto2.decrypt(encrypted, pin, this.deviceFingerprint);
5846
+ }
5847
+ async generateDeviceFingerprint() {
5848
+ const { DeviceFingerprintGenerator: DeviceFingerprintGenerator2 } = await Promise.resolve().then(() => (init_device_bound_crypto(), device_bound_crypto_exports));
5849
+ const fingerprint = await DeviceFingerprintGenerator2.generate();
5850
+ return fingerprint.fingerprint;
5851
+ }
5852
+ async promptForPIN(message) {
5853
+ if (typeof window !== "undefined" && window.prompt) {
5854
+ const pin = window.prompt(message);
5855
+ if (!pin) {
5856
+ throw new Error("PIN required");
5857
+ }
5858
+ return pin;
5859
+ }
5860
+ throw new Error("PIN provider not configured and no browser prompt available");
5861
+ }
5862
+ async getSolanaWeb3() {
5863
+ try {
5864
+ return await import("@solana/web3.js");
5865
+ } catch {
5866
+ throw new Error("@solana/web3.js not installed. Install it to use device-bound payments.");
5867
+ }
5868
+ }
5869
+ };
5870
+ async function setupQuickSessionKey(client, config) {
5871
+ const { SessionKeyCache: SessionKeyCache2 } = await Promise.resolve().then(() => (init_cache(), cache_exports));
5872
+ const lifecycle = new SessionKeyLifecycle(client, {
5873
+ cache: new SessionKeyCache2({ storage: "localStorage", ttl: 36e5 }),
5874
+ autoCleanup: true
5875
+ });
5876
+ await lifecycle.createAndFund({
5877
+ userWallet: config.userWallet,
5878
+ agentId: config.agentId,
5879
+ limitUsdc: config.budgetUsdc,
5880
+ onApprovalNeeded: config.onApproval
5881
+ });
5882
+ return lifecycle;
5883
+ }
5884
+
5885
+ // src/helpers/dev.ts
5886
+ var DevTools = class {
5887
+ static debugEnabled = false;
5888
+ static requestLog = [];
5889
+ /**
5890
+ * Enable debug mode (logs all API requests/responses)
5891
+ */
5892
+ static enableDebugMode() {
5893
+ if (this.isDevelopment()) {
5894
+ this.debugEnabled = true;
5895
+ console.log("\u{1F527} ZendFi Debug Mode: ENABLED");
5896
+ console.log("All API requests will be logged to console");
5897
+ } else {
5898
+ console.warn("Debug mode can only be enabled in development environment");
5899
+ }
5900
+ }
5901
+ /**
5902
+ * Disable debug mode
5903
+ */
5904
+ static disableDebugMode() {
5905
+ this.debugEnabled = false;
5906
+ console.log("\u{1F527} ZendFi Debug Mode: DISABLED");
5907
+ }
5908
+ /**
5909
+ * Check if debug mode is enabled
5910
+ */
5911
+ static isDebugEnabled() {
5912
+ return this.debugEnabled;
5913
+ }
5914
+ /**
5915
+ * Log API request
5916
+ */
5917
+ static logRequest(method, url, body) {
5918
+ if (!this.debugEnabled) return;
5919
+ const timestamp = /* @__PURE__ */ new Date();
5920
+ console.group(`\u{1F4E4} API Request: ${method} ${url}`);
5921
+ console.log("Time:", timestamp.toISOString());
5922
+ if (body) {
5923
+ console.log("Body:", body);
5924
+ }
5925
+ console.groupEnd();
5926
+ this.requestLog.push({ timestamp, method, url });
5927
+ }
5928
+ /**
5929
+ * Log API response
5930
+ */
5931
+ static logResponse(method, url, status, data, duration) {
5932
+ if (!this.debugEnabled) return;
5933
+ const emoji = status >= 200 && status < 300 ? "\u2705" : "\u274C";
5934
+ console.group(`${emoji} API Response: ${method} ${url} [${status}]`);
5935
+ if (duration) {
5936
+ console.log("Duration:", `${duration}ms`);
5937
+ }
5938
+ console.log("Data:", data);
5939
+ console.groupEnd();
5940
+ const lastRequest = this.requestLog[this.requestLog.length - 1];
5941
+ if (lastRequest && lastRequest.method === method && lastRequest.url === url) {
5942
+ lastRequest.status = status;
5943
+ lastRequest.duration = duration;
5944
+ }
5945
+ }
5946
+ /**
5947
+ * Get request log
5948
+ */
5949
+ static getRequestLog() {
5950
+ return [...this.requestLog];
5951
+ }
5952
+ /**
5953
+ * Clear request log
5954
+ */
5955
+ static clearRequestLog() {
5956
+ this.requestLog = [];
5957
+ console.log("\u{1F5D1}\uFE0F Request log cleared");
5958
+ }
5959
+ /**
5960
+ * Create a test session key (devnet only)
5961
+ */
5962
+ static async createTestSessionKey() {
5963
+ if (!this.isDevelopment()) {
5964
+ throw new Error("Test session keys can only be created in development");
5965
+ }
5966
+ const { Keypair: Keypair2 } = await this.getSolanaWeb3();
5967
+ const keypair = Keypair2.generate();
5968
+ return {
5969
+ sessionKeyId: this.generateTestId("sk_test"),
5970
+ sessionWallet: keypair.publicKey.toString(),
5971
+ privateKey: keypair.secretKey,
5972
+ budget: 10
5973
+ // $10 test budget
5974
+ };
5975
+ }
5976
+ /**
5977
+ * Create a mock wallet for testing
5978
+ */
5979
+ static mockWallet(address) {
5980
+ const mockAddress = address || this.generateTestAddress();
5981
+ return {
5982
+ address: mockAddress,
5983
+ publicKey: { toString: () => mockAddress },
5984
+ signTransaction: async (tx) => {
5985
+ console.log("\u{1F527} Mock wallet: Signing transaction");
5986
+ return tx;
5987
+ },
5988
+ signMessage: async (_msg) => {
5989
+ console.log("\u{1F527} Mock wallet: Signing message");
5990
+ return {
5991
+ signature: new Uint8Array(64)
5992
+ // Mock signature
5993
+ };
5994
+ },
5995
+ isConnected: () => true,
5996
+ disconnect: async () => {
5997
+ console.log("\u{1F527} Mock wallet: Disconnected");
5998
+ }
5999
+ };
6000
+ }
6001
+ /**
6002
+ * Log transaction flow (visual diagram in console)
6003
+ */
6004
+ static logTransactionFlow(paymentId) {
6005
+ console.log(`
6006
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
6007
+ \u2551 TRANSACTION FLOW \u2551
6008
+ \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
6009
+ \u2551 \u2551
6010
+ \u2551 Payment ID: ${paymentId} \u2551
6011
+ \u2551 \u2551
6012
+ \u2551 1. \u{1F3D7}\uFE0F Create Payment Intent \u2551
6013
+ \u2551 \u2514\u2500> POST /api/v1/ai/smart-payment \u2551
6014
+ \u2551 \u2551
6015
+ \u2551 2. \u{1F510} Sign Transaction (Device-Bound) \u2551
6016
+ \u2551 \u2514\u2500> Client-side signing with cached keypair \u2551
6017
+ \u2551 \u2551
6018
+ \u2551 3. \u{1F4E4} Submit Signed Transaction \u2551
6019
+ \u2551 \u2514\u2500> POST /api/v1/ai/payments/{id}/submit-signed \u2551
6020
+ \u2551 \u2551
6021
+ \u2551 4. \u23F3 Wait for Blockchain Confirmation \u2551
6022
+ \u2551 \u2514\u2500> Poll Solana RPC (~30-60 seconds) \u2551
6023
+ \u2551 \u2551
6024
+ \u2551 5. \u2705 Payment Confirmed \u2551
6025
+ \u2551 \u2514\u2500> Webhook fired: payment.confirmed \u2551
6026
+ \u2551 \u2551
6027
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
6028
+ `);
6029
+ }
6030
+ /**
6031
+ * Log session key lifecycle
6032
+ */
6033
+ static logSessionKeyLifecycle(sessionKeyId) {
6034
+ console.log(`
6035
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
6036
+ \u2551 SESSION KEY LIFECYCLE \u2551
6037
+ \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
6038
+ \u2551 \u2551
6039
+ \u2551 Session Key ID: ${sessionKeyId} \u2551
6040
+ \u2551 \u2551
6041
+ \u2551 Phase 1: CREATION \u2551
6042
+ \u2551 \u251C\u2500 Generate keypair (client-side) \u2551
6043
+ \u2551 \u251C\u2500 Encrypt with PIN + device fingerprint \u2551
6044
+ \u2551 \u251C\u2500 Send encrypted blob to backend \u2551
6045
+ \u2551 \u2514\u2500 Session key record created \u2551
6046
+ \u2551 \u2551
6047
+ \u2551 Phase 2: FUNDING \u2551
6048
+ \u2551 \u251C\u2500 Create top-up transaction \u2551
6049
+ \u2551 \u251C\u2500 User signs with main wallet \u2551
6050
+ \u2551 \u251C\u2500 Submit to Solana \u2551
6051
+ \u2551 \u2514\u2500 Session wallet funded \u2551
6052
+ \u2551 \u2551
6053
+ \u2551 Phase 3: USAGE \u2551
6054
+ \u2551 \u251C\u2500 Decrypt keypair with PIN \u2551
6055
+ \u2551 \u251C\u2500 Cache for 30min/1hr/24hr \u2551
6056
+ \u2551 \u251C\u2500 Sign payments automatically \u2551
6057
+ \u2551 \u2514\u2500 Track spending against limit \u2551
6058
+ \u2551 \u2551
6059
+ \u2551 Phase 4: REVOCATION \u2551
6060
+ \u2551 \u251C\u2500 Mark as inactive in DB \u2551
6061
+ \u2551 \u251C\u2500 Clear local cache \u2551
6062
+ \u2551 \u2514\u2500 Remaining funds locked \u2551
6063
+ \u2551 \u2551
6064
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
6065
+ `);
6066
+ }
6067
+ /**
6068
+ * Benchmark API request
6069
+ */
6070
+ static async benchmarkRequest(name, fn) {
6071
+ const start = performance.now();
6072
+ const result = await fn();
6073
+ const duration = performance.now() - start;
6074
+ console.log(`\u23F1\uFE0F Benchmark [${name}]: ${duration.toFixed(2)}ms`);
6075
+ return {
6076
+ result,
6077
+ durationMs: duration
6078
+ };
6079
+ }
6080
+ /**
6081
+ * Stress test (send multiple concurrent requests)
6082
+ */
6083
+ static async stressTest(name, fn, concurrency = 10, iterations = 100) {
6084
+ console.log(`\u{1F525} Stress Test: ${name}`);
6085
+ console.log(`Concurrency: ${concurrency}, Iterations: ${iterations}`);
6086
+ const durations = [];
6087
+ let successful = 0;
6088
+ let failed = 0;
6089
+ for (let i = 0; i < iterations; i += concurrency) {
6090
+ const batch = Array(Math.min(concurrency, iterations - i)).fill(null).map(() => this.benchmarkRequest(`${name}-${i}`, fn));
6091
+ const results = await Promise.allSettled(batch);
6092
+ results.forEach((result) => {
6093
+ if (result.status === "fulfilled") {
6094
+ successful++;
6095
+ durations.push(result.value.durationMs);
6096
+ } else {
6097
+ failed++;
6098
+ }
6099
+ });
6100
+ }
6101
+ const stats = {
6102
+ totalRequests: iterations,
6103
+ successful,
6104
+ failed,
6105
+ avgDurationMs: durations.reduce((a, b) => a + b, 0) / durations.length,
6106
+ minDurationMs: Math.min(...durations),
6107
+ maxDurationMs: Math.max(...durations)
6108
+ };
6109
+ console.table(stats);
6110
+ return stats;
6111
+ }
6112
+ /**
6113
+ * Inspect ZendFi SDK configuration
6114
+ */
6115
+ static inspectConfig(client) {
6116
+ console.group("\u{1F50D} ZendFi SDK Configuration");
6117
+ console.log("Base URL:", client.config?.baseURL || "Unknown");
6118
+ console.log("API Key:", client.config?.apiKey ? `${client.config.apiKey.slice(0, 10)}...` : "Not set");
6119
+ console.log("Mode:", client.config?.mode || "Unknown");
6120
+ console.log("Environment:", client.config?.environment || "Unknown");
6121
+ console.log("Timeout:", client.config?.timeout || "Default");
6122
+ console.groupEnd();
6123
+ }
6124
+ /**
6125
+ * Generate test data
6126
+ */
6127
+ static generateTestData() {
6128
+ return {
6129
+ userWallet: this.generateTestAddress(),
6130
+ agentId: `test-agent-${Date.now()}`,
6131
+ sessionKeyId: this.generateTestId("sk_test"),
6132
+ paymentId: this.generateTestId("pay_test")
6133
+ };
6134
+ }
6135
+ /**
6136
+ * Check if running in development environment
6137
+ */
6138
+ static isDevelopment() {
6139
+ if (typeof process !== "undefined" && process.env) {
6140
+ return process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test";
6141
+ }
6142
+ if (typeof window !== "undefined" && window.location) {
6143
+ return window.location.hostname === "localhost" || window.location.hostname.includes("dev") || window.location.hostname.includes("staging");
6144
+ }
6145
+ return false;
6146
+ }
6147
+ /**
6148
+ * Generate test Solana address
6149
+ */
6150
+ static generateTestAddress() {
6151
+ const chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
6152
+ let address = "";
6153
+ for (let i = 0; i < 44; i++) {
6154
+ address += chars[Math.floor(Math.random() * chars.length)];
6155
+ }
6156
+ return address;
6157
+ }
6158
+ /**
6159
+ * Generate test ID with prefix
6160
+ */
6161
+ static generateTestId(prefix) {
6162
+ const id = Array(32).fill(null).map(() => Math.floor(Math.random() * 16).toString(16)).join("");
6163
+ return `${prefix}_${id}`;
6164
+ }
6165
+ /**
6166
+ * Get Solana Web3.js
6167
+ */
6168
+ static async getSolanaWeb3() {
6169
+ try {
6170
+ return await import("@solana/web3.js");
6171
+ } catch {
6172
+ throw new Error("@solana/web3.js not installed");
6173
+ }
6174
+ }
6175
+ };
6176
+ var PerformanceMonitor = class {
6177
+ metrics = /* @__PURE__ */ new Map();
6178
+ /**
6179
+ * Record a metric
6180
+ */
6181
+ record(name, value) {
6182
+ const values = this.metrics.get(name) || [];
6183
+ values.push(value);
6184
+ this.metrics.set(name, values);
6185
+ }
6186
+ /**
6187
+ * Get statistics for a metric
6188
+ */
6189
+ getStats(name) {
6190
+ const values = this.metrics.get(name);
6191
+ if (!values || values.length === 0) return null;
6192
+ const sorted = [...values].sort((a, b) => a - b);
6193
+ const count = values.length;
6194
+ return {
6195
+ count,
6196
+ avg: values.reduce((a, b) => a + b, 0) / count,
6197
+ min: sorted[0],
6198
+ max: sorted[count - 1],
6199
+ p50: sorted[Math.floor(count * 0.5)],
6200
+ p95: sorted[Math.floor(count * 0.95)],
6201
+ p99: sorted[Math.floor(count * 0.99)]
6202
+ };
6203
+ }
6204
+ /**
6205
+ * Get all metrics
6206
+ */
6207
+ getAllStats() {
6208
+ const stats = {};
6209
+ for (const [name] of this.metrics) {
6210
+ stats[name] = this.getStats(name);
6211
+ }
6212
+ return stats;
6213
+ }
6214
+ /**
6215
+ * Print report
6216
+ */
6217
+ printReport() {
6218
+ console.table(this.getAllStats());
6219
+ }
6220
+ /**
6221
+ * Clear all metrics
6222
+ */
6223
+ clear() {
6224
+ this.metrics.clear();
6225
+ }
6226
+ };
3670
6227
  // Annotate the CommonJS export names for ESM import in node:
3671
6228
  0 && (module.exports = {
3672
6229
  AgentAPI,
6230
+ AnthropicAdapter,
3673
6231
  ApiError,
3674
6232
  AuthenticationError,
3675
6233
  AutonomyAPI,
3676
6234
  ConfigLoader,
6235
+ DevTools,
3677
6236
  DeviceBoundSessionKey,
3678
6237
  DeviceFingerprintGenerator,
3679
6238
  ERROR_CODES,
6239
+ ErrorRecovery,
6240
+ GeminiAdapter,
3680
6241
  InterceptorManager,
3681
6242
  LitCryptoSigner,
3682
6243
  NetworkError,
6244
+ OpenAIAdapter,
6245
+ PINRateLimiter,
6246
+ PINValidator,
3683
6247
  PaymentError,
6248
+ PaymentIntentParser,
3684
6249
  PaymentIntentsAPI,
6250
+ PerformanceMonitor,
3685
6251
  PricingAPI,
6252
+ QuickCaches,
3686
6253
  RateLimitError,
3687
6254
  RateLimiter,
3688
6255
  RecoveryQRGenerator,
6256
+ RetryStrategy,
3689
6257
  SPENDING_LIMIT_ACTION_CID,
6258
+ SecureStorage,
6259
+ SessionKeyCache,
3690
6260
  SessionKeyCrypto,
6261
+ SessionKeyLifecycle,
3691
6262
  SessionKeysAPI,
3692
6263
  SmartPaymentsAPI,
6264
+ TransactionMonitor,
6265
+ TransactionPoller,
3693
6266
  ValidationError,
6267
+ WalletConnector,
3694
6268
  WebhookError,
3695
6269
  ZendFiClient,
3696
6270
  ZendFiError,
@@ -3705,6 +6279,7 @@ function decodeSignatureFromLit(result) {
3705
6279
  asPaymentLinkCode,
3706
6280
  asSessionId,
3707
6281
  asSubscriptionId,
6282
+ createWalletHook,
3708
6283
  createZendFiError,
3709
6284
  decodeSignatureFromLit,
3710
6285
  encodeTransactionForLit,
@@ -3712,6 +6287,7 @@ function decodeSignatureFromLit(result) {
3712
6287
  isZendFiError,
3713
6288
  processWebhook,
3714
6289
  requiresLitSigning,
6290
+ setupQuickSessionKey,
3715
6291
  sleep,
3716
6292
  verifyExpressWebhook,
3717
6293
  verifyNextWebhook,