@zendfi/sdk 0.5.8 → 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.mjs CHANGED
@@ -1,7 +1,19 @@
1
1
  import {
2
- __require,
3
2
  processWebhook
4
- } from "./chunk-YFOBPGQE.mjs";
3
+ } from "./chunk-3ACJUM6V.mjs";
4
+ import {
5
+ DeviceBoundSessionKey,
6
+ DeviceFingerprintGenerator,
7
+ RecoveryQRGenerator,
8
+ SessionKeyCrypto
9
+ } from "./chunk-XERHBDUK.mjs";
10
+ import {
11
+ QuickCaches,
12
+ SessionKeyCache
13
+ } from "./chunk-5O5NAX65.mjs";
14
+ import {
15
+ __require
16
+ } from "./chunk-Y6FXYEAI.mjs";
5
17
 
6
18
  // src/client.ts
7
19
  import fetch2 from "cross-fetch";
@@ -1199,6 +1211,43 @@ var AutonomyAPI = class {
1199
1211
  throw new Error("delegation_signature must be base64 encoded");
1200
1212
  }
1201
1213
  }
1214
+ /**
1215
+ * Get spending attestations for a delegate (audit trail)
1216
+ *
1217
+ * Returns all cryptographically signed attestations ZendFi created for
1218
+ * this delegate. Each attestation contains:
1219
+ * - The spending state at the time of payment
1220
+ * - ZendFi's Ed25519 signature
1221
+ * - Timestamp and nonce (for replay protection)
1222
+ *
1223
+ * These attestations can be independently verified using ZendFi's public key
1224
+ * to confirm spending limit enforcement was applied correctly.
1225
+ *
1226
+ * @param delegateId - UUID of the autonomous delegate
1227
+ * @returns Attestation audit response with all signed attestations
1228
+ *
1229
+ * @example
1230
+ * ```typescript
1231
+ * const audit = await zendfi.autonomy.getAttestations('delegate_123...');
1232
+ *
1233
+ * console.log(`Found ${audit.attestation_count} attestations`);
1234
+ * console.log(`ZendFi public key: ${audit.zendfi_attestation_public_key}`);
1235
+ *
1236
+ * // Verify each attestation independently
1237
+ * for (const signed of audit.attestations) {
1238
+ * console.log(`Payment ${signed.attestation.payment_id}:`);
1239
+ * console.log(` Requested: $${signed.attestation.requested_usd}`);
1240
+ * console.log(` Remaining after: $${signed.attestation.remaining_after_usd}`);
1241
+ * // Verify signature with nacl.sign.detached.verify()
1242
+ * }
1243
+ * ```
1244
+ */
1245
+ async getAttestations(delegateId) {
1246
+ return this.request(
1247
+ "GET",
1248
+ `/api/v1/ai/delegates/${delegateId}/attestations`
1249
+ );
1250
+ }
1202
1251
  };
1203
1252
 
1204
1253
  // src/api/smart-payments.ts
@@ -2386,589 +2435,8 @@ function verifyWebhookSignature(payload, signature, secret) {
2386
2435
  });
2387
2436
  }
2388
2437
 
2389
- // src/device-bound-crypto.ts
2390
- import { Keypair } from "@solana/web3.js";
2391
- import * as crypto from "crypto";
2392
- var DeviceFingerprintGenerator = class {
2393
- /**
2394
- * Generate a unique device fingerprint
2395
- * Combines multiple browser attributes for uniqueness
2396
- */
2397
- static async generate() {
2398
- const components = {};
2399
- try {
2400
- components.canvas = await this.getCanvasFingerprint();
2401
- components.webgl = await this.getWebGLFingerprint();
2402
- components.audio = await this.getAudioFingerprint();
2403
- components.screen = `${screen.width}x${screen.height}x${screen.colorDepth}`;
2404
- components.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
2405
- components.languages = navigator.languages?.join(",") || navigator.language;
2406
- components.platform = navigator.platform;
2407
- components.hardwareConcurrency = navigator.hardwareConcurrency?.toString() || "unknown";
2408
- const combined = Object.entries(components).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}:${value}`).join("|");
2409
- const fingerprint = await this.sha256(combined);
2410
- return {
2411
- fingerprint,
2412
- generatedAt: Date.now(),
2413
- components
2414
- };
2415
- } catch (error) {
2416
- console.warn("Device fingerprinting failed, using fallback", error);
2417
- return this.generateFallbackFingerprint();
2418
- }
2419
- }
2420
- /**
2421
- * Graceful fallback fingerprint generation
2422
- * Works in headless browsers, SSR, and restricted environments
2423
- */
2424
- static async generateFallbackFingerprint() {
2425
- const components = {};
2426
- try {
2427
- if (typeof navigator !== "undefined") {
2428
- components.platform = navigator.platform || "unknown";
2429
- components.languages = navigator.languages?.join(",") || navigator.language || "unknown";
2430
- components.hardwareConcurrency = navigator.hardwareConcurrency?.toString() || "unknown";
2431
- }
2432
- if (typeof screen !== "undefined") {
2433
- components.screen = `${screen.width || 0}x${screen.height || 0}x${screen.colorDepth || 0}`;
2434
- }
2435
- if (typeof Intl !== "undefined") {
2436
- try {
2437
- components.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
2438
- } catch {
2439
- components.timezone = "unknown";
2440
- }
2441
- }
2442
- } catch {
2443
- components.platform = "fallback";
2444
- }
2445
- let randomEntropy = "";
2446
- try {
2447
- if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
2448
- const arr = new Uint8Array(16);
2449
- window.crypto.getRandomValues(arr);
2450
- randomEntropy = Array.from(arr).map((b) => b.toString(16).padStart(2, "0")).join("");
2451
- } else if (typeof crypto !== "undefined" && crypto.randomBytes) {
2452
- randomEntropy = crypto.randomBytes(16).toString("hex");
2453
- }
2454
- } catch {
2455
- randomEntropy = Date.now().toString(36) + Math.random().toString(36);
2456
- }
2457
- const combined = Object.entries(components).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}:${value}`).join("|") + "|entropy:" + randomEntropy;
2458
- const fingerprint = await this.sha256(combined);
2459
- return {
2460
- fingerprint,
2461
- generatedAt: Date.now(),
2462
- components
2463
- };
2464
- }
2465
- static async getCanvasFingerprint() {
2466
- const canvas = document.createElement("canvas");
2467
- const ctx = canvas.getContext("2d");
2468
- if (!ctx) return "no-canvas";
2469
- canvas.width = 200;
2470
- canvas.height = 50;
2471
- ctx.textBaseline = "top";
2472
- ctx.font = '14px "Arial"';
2473
- ctx.fillStyle = "#f60";
2474
- ctx.fillRect(0, 0, 100, 50);
2475
- ctx.fillStyle = "#069";
2476
- ctx.fillText("ZendFi \u{1F510}", 2, 2);
2477
- ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
2478
- ctx.fillText("Device-Bound", 4, 17);
2479
- return canvas.toDataURL();
2480
- }
2481
- static async getWebGLFingerprint() {
2482
- const canvas = document.createElement("canvas");
2483
- const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
2484
- if (!gl) return "no-webgl";
2485
- const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
2486
- if (!debugInfo) return "no-debug-info";
2487
- const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
2488
- const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
2489
- return `${vendor}|${renderer}`;
2490
- }
2491
- static async getAudioFingerprint() {
2492
- try {
2493
- const AudioContext = window.AudioContext || window.webkitAudioContext;
2494
- if (!AudioContext) return "no-audio";
2495
- const context = new AudioContext();
2496
- const oscillator = context.createOscillator();
2497
- const analyser = context.createAnalyser();
2498
- const gainNode = context.createGain();
2499
- const scriptProcessor = context.createScriptProcessor(4096, 1, 1);
2500
- gainNode.gain.value = 0;
2501
- oscillator.connect(analyser);
2502
- analyser.connect(scriptProcessor);
2503
- scriptProcessor.connect(gainNode);
2504
- gainNode.connect(context.destination);
2505
- oscillator.start(0);
2506
- return new Promise((resolve) => {
2507
- scriptProcessor.onaudioprocess = (event) => {
2508
- const output = event.inputBuffer.getChannelData(0);
2509
- const hash = Array.from(output.slice(0, 30)).reduce((acc, val) => acc + Math.abs(val), 0);
2510
- oscillator.stop();
2511
- scriptProcessor.disconnect();
2512
- context.close();
2513
- resolve(hash.toString());
2514
- };
2515
- });
2516
- } catch (error) {
2517
- return "audio-error";
2518
- }
2519
- }
2520
- static async sha256(data) {
2521
- if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
2522
- const encoder = new TextEncoder();
2523
- const dataBuffer = encoder.encode(data);
2524
- const hashBuffer = await window.crypto.subtle.digest("SHA-256", dataBuffer);
2525
- const hashArray = Array.from(new Uint8Array(hashBuffer));
2526
- return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
2527
- } else {
2528
- return crypto.createHash("sha256").update(data).digest("hex");
2529
- }
2530
- }
2531
- };
2532
- var SessionKeyCrypto = class {
2533
- /**
2534
- * Encrypt a Solana keypair with PIN + device fingerprint
2535
- * Uses Argon2id for key derivation and AES-256-GCM for encryption
2536
- */
2537
- static async encrypt(keypair, pin, deviceFingerprint) {
2538
- if (!/^\d{6}$/.test(pin)) {
2539
- throw new Error("PIN must be exactly 6 numeric digits");
2540
- }
2541
- const encryptionKey = await this.deriveKey(pin, deviceFingerprint);
2542
- const nonce = this.generateNonce();
2543
- const secretKey = keypair.secretKey;
2544
- const encryptedData = await this.aesEncrypt(secretKey, encryptionKey, nonce);
2545
- return {
2546
- encryptedData: Buffer.from(encryptedData).toString("base64"),
2547
- nonce: Buffer.from(nonce).toString("base64"),
2548
- publicKey: keypair.publicKey.toBase58(),
2549
- deviceFingerprint,
2550
- version: "argon2id-aes256gcm-v1"
2551
- };
2552
- }
2553
- /**
2554
- * Decrypt an encrypted session key with PIN + device fingerprint
2555
- */
2556
- static async decrypt(encrypted, pin, deviceFingerprint) {
2557
- if (!/^\d{6}$/.test(pin)) {
2558
- throw new Error("PIN must be exactly 6 numeric digits");
2559
- }
2560
- if (encrypted.deviceFingerprint !== deviceFingerprint) {
2561
- throw new Error("Device fingerprint mismatch - wrong device or security threat");
2562
- }
2563
- const encryptionKey = await this.deriveKey(pin, deviceFingerprint);
2564
- const encryptedData = Buffer.from(encrypted.encryptedData, "base64");
2565
- const nonce = Buffer.from(encrypted.nonce, "base64");
2566
- try {
2567
- const secretKey = await this.aesDecrypt(encryptedData, encryptionKey, nonce);
2568
- return Keypair.fromSecretKey(secretKey);
2569
- } catch (error) {
2570
- throw new Error("Decryption failed - wrong PIN or corrupted data");
2571
- }
2572
- }
2573
- /**
2574
- * Derive encryption key from PIN + device fingerprint using Argon2id
2575
- *
2576
- * Argon2id parameters (OWASP recommended):
2577
- * - Memory: 64MB (65536 KB)
2578
- * - Iterations: 3
2579
- * - Parallelism: 4
2580
- * - Salt: device fingerprint
2581
- */
2582
- static async deriveKey(pin, deviceFingerprint) {
2583
- if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
2584
- const encoder = new TextEncoder();
2585
- const keyMaterial = await window.crypto.subtle.importKey(
2586
- "raw",
2587
- encoder.encode(pin),
2588
- { name: "PBKDF2" },
2589
- false,
2590
- ["deriveBits"]
2591
- );
2592
- const derivedBits = await window.crypto.subtle.deriveBits(
2593
- {
2594
- name: "PBKDF2",
2595
- salt: encoder.encode(deviceFingerprint),
2596
- iterations: 1e5,
2597
- // High iteration count for security
2598
- hash: "SHA-256"
2599
- },
2600
- keyMaterial,
2601
- 256
2602
- // 256 bits = 32 bytes for AES-256
2603
- );
2604
- return new Uint8Array(derivedBits);
2605
- } else {
2606
- const salt = crypto.createHash("sha256").update(deviceFingerprint).digest();
2607
- return crypto.pbkdf2Sync(pin, salt, 1e5, 32, "sha256");
2608
- }
2609
- }
2610
- /**
2611
- * Generate random nonce for AES-GCM (12 bytes)
2612
- */
2613
- static generateNonce() {
2614
- if (typeof window !== "undefined" && window.crypto) {
2615
- return window.crypto.getRandomValues(new Uint8Array(12));
2616
- } else {
2617
- return crypto.randomBytes(12);
2618
- }
2619
- }
2620
- /**
2621
- * Encrypt with AES-256-GCM
2622
- */
2623
- static async aesEncrypt(plaintext, key, nonce) {
2624
- if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
2625
- const keyBuffer = key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength);
2626
- const nonceBuffer = nonce.buffer.slice(nonce.byteOffset, nonce.byteOffset + nonce.byteLength);
2627
- const plaintextBuffer = plaintext.buffer.slice(plaintext.byteOffset, plaintext.byteOffset + plaintext.byteLength);
2628
- const cryptoKey = await window.crypto.subtle.importKey(
2629
- "raw",
2630
- keyBuffer,
2631
- { name: "AES-GCM" },
2632
- false,
2633
- ["encrypt"]
2634
- );
2635
- const encrypted = await window.crypto.subtle.encrypt(
2636
- {
2637
- name: "AES-GCM",
2638
- iv: nonceBuffer
2639
- },
2640
- cryptoKey,
2641
- plaintextBuffer
2642
- );
2643
- return new Uint8Array(encrypted);
2644
- } else {
2645
- const cipher = crypto.createCipheriv("aes-256-gcm", key, nonce);
2646
- const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
2647
- const authTag = cipher.getAuthTag();
2648
- return new Uint8Array(Buffer.concat([encrypted, authTag]));
2649
- }
2650
- }
2651
- /**
2652
- * Decrypt with AES-256-GCM
2653
- */
2654
- static async aesDecrypt(ciphertext, key, nonce) {
2655
- if (typeof window !== "undefined" && window.crypto && window.crypto.subtle) {
2656
- const keyBuffer = key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength);
2657
- const nonceBuffer = nonce.buffer.slice(nonce.byteOffset, nonce.byteOffset + nonce.byteLength);
2658
- const ciphertextBuffer = ciphertext.buffer.slice(ciphertext.byteOffset, ciphertext.byteOffset + ciphertext.byteLength);
2659
- const cryptoKey = await window.crypto.subtle.importKey(
2660
- "raw",
2661
- keyBuffer,
2662
- { name: "AES-GCM" },
2663
- false,
2664
- ["decrypt"]
2665
- );
2666
- const decrypted = await window.crypto.subtle.decrypt(
2667
- {
2668
- name: "AES-GCM",
2669
- iv: nonceBuffer
2670
- },
2671
- cryptoKey,
2672
- ciphertextBuffer
2673
- );
2674
- return new Uint8Array(decrypted);
2675
- } else {
2676
- const authTag = ciphertext.slice(-16);
2677
- const encrypted = ciphertext.slice(0, -16);
2678
- const decipher = crypto.createDecipheriv("aes-256-gcm", key, nonce);
2679
- decipher.setAuthTag(authTag);
2680
- const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
2681
- return new Uint8Array(decrypted);
2682
- }
2683
- }
2684
- };
2685
- var RecoveryQRGenerator = class {
2686
- /**
2687
- * Generate recovery QR data
2688
- * This allows users to recover their session key on a new device
2689
- */
2690
- static generate(encrypted) {
2691
- return {
2692
- encryptedSessionKey: encrypted.encryptedData,
2693
- nonce: encrypted.nonce,
2694
- publicKey: encrypted.publicKey,
2695
- version: "v1",
2696
- createdAt: Date.now()
2697
- };
2698
- }
2699
- /**
2700
- * Encode recovery QR as JSON string
2701
- */
2702
- static encode(recoveryQR) {
2703
- return JSON.stringify(recoveryQR);
2704
- }
2705
- /**
2706
- * Decode recovery QR from JSON string
2707
- */
2708
- static decode(qrData) {
2709
- try {
2710
- const parsed = JSON.parse(qrData);
2711
- if (!parsed.encryptedSessionKey || !parsed.nonce || !parsed.publicKey) {
2712
- throw new Error("Invalid recovery QR data");
2713
- }
2714
- return parsed;
2715
- } catch (error) {
2716
- throw new Error("Failed to decode recovery QR");
2717
- }
2718
- }
2719
- /**
2720
- * Re-encrypt session key for new device
2721
- */
2722
- static async reEncryptForNewDevice(recoveryQR, oldPin, oldDeviceFingerprint, newPin, newDeviceFingerprint) {
2723
- const oldEncrypted = {
2724
- encryptedData: recoveryQR.encryptedSessionKey,
2725
- nonce: recoveryQR.nonce,
2726
- publicKey: recoveryQR.publicKey,
2727
- deviceFingerprint: oldDeviceFingerprint,
2728
- version: "argon2id-aes256gcm-v1"
2729
- };
2730
- const keypair = await SessionKeyCrypto.decrypt(oldEncrypted, oldPin, oldDeviceFingerprint);
2731
- return await SessionKeyCrypto.encrypt(keypair, newPin, newDeviceFingerprint);
2732
- }
2733
- };
2734
- var DeviceBoundSessionKey = class _DeviceBoundSessionKey {
2735
- encrypted = null;
2736
- deviceFingerprint = null;
2737
- sessionKeyId = null;
2738
- recoveryQR = null;
2739
- // Auto-signing cache: decrypted keypair stored in memory
2740
- // Enables instant signing without re-entering PIN for subsequent payments
2741
- cachedKeypair = null;
2742
- cacheExpiry = null;
2743
- // Timestamp when cache expires
2744
- DEFAULT_CACHE_TTL_MS = 30 * 60 * 1e3;
2745
- // 30 minutes
2746
- /**
2747
- * Create a new device-bound session key
2748
- */
2749
- static async create(options) {
2750
- const deviceFingerprint = await DeviceFingerprintGenerator.generate();
2751
- const keypair = Keypair.generate();
2752
- const encrypted = await SessionKeyCrypto.encrypt(
2753
- keypair,
2754
- options.pin,
2755
- deviceFingerprint.fingerprint
2756
- );
2757
- const instance = new _DeviceBoundSessionKey();
2758
- instance.encrypted = encrypted;
2759
- instance.deviceFingerprint = deviceFingerprint;
2760
- if (options.generateRecoveryQR) {
2761
- instance.recoveryQR = RecoveryQRGenerator.generate(encrypted);
2762
- }
2763
- return instance;
2764
- }
2765
- /**
2766
- * Get encrypted data for backend storage
2767
- */
2768
- getEncryptedData() {
2769
- if (!this.encrypted) {
2770
- throw new Error("Session key not created yet");
2771
- }
2772
- return this.encrypted;
2773
- }
2774
- /**
2775
- * Get device fingerprint
2776
- */
2777
- getDeviceFingerprint() {
2778
- if (!this.deviceFingerprint) {
2779
- throw new Error("Device fingerprint not generated yet");
2780
- }
2781
- return this.deviceFingerprint.fingerprint;
2782
- }
2783
- /**
2784
- * Get public key
2785
- */
2786
- getPublicKey() {
2787
- if (!this.encrypted) {
2788
- throw new Error("Session key not created yet");
2789
- }
2790
- return this.encrypted.publicKey;
2791
- }
2792
- /**
2793
- * Get recovery QR data (if generated)
2794
- */
2795
- getRecoveryQR() {
2796
- return this.recoveryQR;
2797
- }
2798
- /**
2799
- * Decrypt and sign a transaction
2800
- *
2801
- * @param transaction - The transaction to sign
2802
- * @param pin - User's PIN (required only if keypair not cached)
2803
- * @param cacheKeypair - Whether to cache the decrypted keypair for future use (default: true)
2804
- * @param cacheTTL - Cache time-to-live in milliseconds (default: 30 minutes)
2805
- *
2806
- * @example
2807
- * ```typescript
2808
- * // First payment: requires PIN, caches keypair
2809
- * await sessionKey.signTransaction(tx1, '123456', true);
2810
- *
2811
- * // Subsequent payments: uses cached keypair, no PIN needed!
2812
- * await sessionKey.signTransaction(tx2, '', false); // PIN ignored if cached
2813
- *
2814
- * // Clear cache when done
2815
- * sessionKey.clearCache();
2816
- * ```
2817
- */
2818
- async signTransaction(transaction, pin = "", cacheKeypair = true, cacheTTL) {
2819
- if (!this.encrypted || !this.deviceFingerprint) {
2820
- throw new Error("Session key not initialized");
2821
- }
2822
- let keypair;
2823
- if (this.isCached()) {
2824
- keypair = this.cachedKeypair;
2825
- if (typeof console !== "undefined") {
2826
- console.log("\u{1F680} Using cached keypair - instant signing (no PIN required)");
2827
- }
2828
- } else {
2829
- if (!pin) {
2830
- throw new Error("PIN required: no cached keypair available");
2831
- }
2832
- keypair = await SessionKeyCrypto.decrypt(
2833
- this.encrypted,
2834
- pin,
2835
- this.deviceFingerprint.fingerprint
2836
- );
2837
- if (cacheKeypair) {
2838
- const ttl = cacheTTL || this.DEFAULT_CACHE_TTL_MS;
2839
- this.cacheKeypair(keypair, ttl);
2840
- if (typeof console !== "undefined") {
2841
- console.log(`\u2705 Keypair decrypted and cached for ${ttl / 1e3 / 60} minutes`);
2842
- }
2843
- }
2844
- }
2845
- transaction.sign(keypair);
2846
- return transaction;
2847
- }
2848
- /**
2849
- * Check if keypair is cached and valid
2850
- */
2851
- isCached() {
2852
- if (!this.cachedKeypair || !this.cacheExpiry) {
2853
- return false;
2854
- }
2855
- const now = Date.now();
2856
- if (now > this.cacheExpiry) {
2857
- this.clearCache();
2858
- return false;
2859
- }
2860
- return true;
2861
- }
2862
- /**
2863
- * Manually cache a keypair
2864
- * Called internally after PIN decryption
2865
- */
2866
- cacheKeypair(keypair, ttl) {
2867
- this.cachedKeypair = keypair;
2868
- this.cacheExpiry = Date.now() + ttl;
2869
- }
2870
- /**
2871
- * Clear cached keypair
2872
- * Should be called when user logs out or session ends
2873
- *
2874
- * @example
2875
- * ```typescript
2876
- * // Clear cache on logout
2877
- * sessionKey.clearCache();
2878
- *
2879
- * // Or clear automatically on tab close
2880
- * window.addEventListener('beforeunload', () => {
2881
- * sessionKey.clearCache();
2882
- * });
2883
- * ```
2884
- */
2885
- clearCache() {
2886
- this.cachedKeypair = null;
2887
- this.cacheExpiry = null;
2888
- if (typeof console !== "undefined") {
2889
- console.log("\u{1F9F9} Keypair cache cleared");
2890
- }
2891
- }
2892
- /**
2893
- * Decrypt and cache keypair without signing a transaction
2894
- * Useful for pre-warming the cache before user makes payments
2895
- *
2896
- * @example
2897
- * ```typescript
2898
- * // After session key creation, decrypt and cache
2899
- * await sessionKey.unlockWithPin('123456');
2900
- *
2901
- * // Now all subsequent payments are instant (no PIN)
2902
- * await sessionKey.signTransaction(tx1, '', false); // Instant!
2903
- * await sessionKey.signTransaction(tx2, '', false); // Instant!
2904
- * ```
2905
- */
2906
- async unlockWithPin(pin, cacheTTL) {
2907
- if (!this.encrypted || !this.deviceFingerprint) {
2908
- throw new Error("Session key not initialized");
2909
- }
2910
- const keypair = await SessionKeyCrypto.decrypt(
2911
- this.encrypted,
2912
- pin,
2913
- this.deviceFingerprint.fingerprint
2914
- );
2915
- const ttl = cacheTTL || this.DEFAULT_CACHE_TTL_MS;
2916
- this.cacheKeypair(keypair, ttl);
2917
- if (typeof console !== "undefined") {
2918
- console.log(`\u{1F513} Session key unlocked and cached for ${ttl / 1e3 / 60} minutes`);
2919
- }
2920
- }
2921
- /**
2922
- * Get time remaining until cache expires (in milliseconds)
2923
- * Returns 0 if not cached
2924
- */
2925
- getCacheTimeRemaining() {
2926
- if (!this.isCached() || !this.cacheExpiry) {
2927
- return 0;
2928
- }
2929
- const remaining = this.cacheExpiry - Date.now();
2930
- return Math.max(0, remaining);
2931
- }
2932
- /**
2933
- * Extend cache expiry time
2934
- * Useful to keep session active during user activity
2935
- *
2936
- * @example
2937
- * ```typescript
2938
- * // Extend cache by 15 minutes on each payment
2939
- * await sessionKey.signTransaction(tx, '');
2940
- * sessionKey.extendCache(15 * 60 * 1000);
2941
- * ```
2942
- */
2943
- extendCache(additionalTTL) {
2944
- if (!this.isCached()) {
2945
- throw new Error("Cannot extend cache: no cached keypair");
2946
- }
2947
- this.cacheExpiry += additionalTTL;
2948
- if (typeof console !== "undefined") {
2949
- const remainingMinutes = this.getCacheTimeRemaining() / 1e3 / 60;
2950
- console.log(`\u23F0 Cache extended - ${remainingMinutes.toFixed(1)} minutes remaining`);
2951
- }
2952
- }
2953
- /**
2954
- * Set session key ID after backend creation
2955
- */
2956
- setSessionKeyId(id) {
2957
- this.sessionKeyId = id;
2958
- }
2959
- /**
2960
- * Get session key ID
2961
- */
2962
- getSessionKeyId() {
2963
- if (!this.sessionKeyId) {
2964
- throw new Error("Session key not registered with backend");
2965
- }
2966
- return this.sessionKeyId;
2967
- }
2968
- };
2969
-
2970
2438
  // src/device-bound-session-keys.ts
2971
- import { Transaction as Transaction2 } from "@solana/web3.js";
2439
+ import { Transaction } from "@solana/web3.js";
2972
2440
  var ZendFiSessionKeyManager = class {
2973
2441
  baseURL;
2974
2442
  apiKey;
@@ -2987,6 +2455,8 @@ var ZendFiSessionKeyManager = class {
2987
2455
  *
2988
2456
  * const sessionKey = await manager.createSessionKey({
2989
2457
  * userWallet: '7xKNH....',
2458
+ * agentId: 'shopping-assistant-v1',
2459
+ * agentName: 'AI Shopping Assistant',
2990
2460
  * limitUSDC: 100,
2991
2461
  * durationDays: 7,
2992
2462
  * pin: '123456',
@@ -2994,6 +2464,7 @@ var ZendFiSessionKeyManager = class {
2994
2464
  * });
2995
2465
  *
2996
2466
  * console.log('Session key created:', sessionKey.sessionKeyId);
2467
+ * console.log('Works across all apps with agent:', sessionKey.agentId);
2997
2468
  * console.log('Recovery QR:', sessionKey.recoveryQR);
2998
2469
  * ```
2999
2470
  */
@@ -3013,6 +2484,8 @@ var ZendFiSessionKeyManager = class {
3013
2484
  }
3014
2485
  const request = {
3015
2486
  userWallet: options.userWallet,
2487
+ agentId: options.agentId,
2488
+ agentName: options.agentName,
3016
2489
  limitUsdc: options.limitUSDC,
3017
2490
  durationDays: options.durationDays,
3018
2491
  encryptedSessionKey: encrypted.encryptedData,
@@ -3031,10 +2504,13 @@ var ZendFiSessionKeyManager = class {
3031
2504
  sessionKey.setSessionKeyId(response.sessionKeyId);
3032
2505
  return {
3033
2506
  sessionKeyId: response.sessionKeyId,
2507
+ agentId: response.agentId,
2508
+ agentName: response.agentName,
3034
2509
  sessionWallet: response.sessionWallet,
3035
2510
  expiresAt: response.expiresAt,
3036
2511
  recoveryQR,
3037
- limitUsdc: response.limitUsdc
2512
+ limitUsdc: response.limitUsdc,
2513
+ crossAppCompatible: response.crossAppCompatible
3038
2514
  };
3039
2515
  }
3040
2516
  /**
@@ -3137,7 +2613,7 @@ var ZendFiSessionKeyManager = class {
3137
2613
  throw new Error("Backend did not return unsigned transaction");
3138
2614
  }
3139
2615
  const transactionBuffer = Buffer.from(paymentResponse.unsigned_transaction, "base64");
3140
- const transaction = Transaction2.from(transactionBuffer);
2616
+ const transaction = Transaction.from(transactionBuffer);
3141
2617
  const signedTransaction = await this.sessionKey.signTransaction(
3142
2618
  transaction,
3143
2619
  options.pin || "",
@@ -3448,29 +2924,2146 @@ function decodeSignatureFromLit(result) {
3448
2924
  }
3449
2925
  return bytes;
3450
2926
  }
2927
+
2928
+ // src/helpers/ai.ts
2929
+ var PaymentIntentParser = class {
2930
+ /**
2931
+ * Parse natural language into structured intent
2932
+ */
2933
+ static parse(text) {
2934
+ if (!text || typeof text !== "string") return null;
2935
+ const lowerText = text.toLowerCase().trim();
2936
+ let action = "chat_only";
2937
+ let confidence = 0;
2938
+ if (this.containsPaymentKeywords(lowerText)) {
2939
+ action = "payment";
2940
+ confidence = 0.7;
2941
+ const amount = this.extractAmount(lowerText);
2942
+ const description = this.extractDescription(lowerText);
2943
+ if (amount && description) {
2944
+ confidence = 0.9;
2945
+ return { action, amount, description, confidence, rawText: text };
2946
+ }
2947
+ if (amount) {
2948
+ confidence = 0.8;
2949
+ return { action, amount, confidence, rawText: text };
2950
+ }
2951
+ }
2952
+ if (this.containsSessionKeywords(lowerText)) {
2953
+ action = "create_session";
2954
+ confidence = 0.8;
2955
+ const amount = this.extractAmount(lowerText);
2956
+ return { action, amount, confidence, rawText: text, description: "Session key budget" };
2957
+ }
2958
+ if (this.containsStatusKeywords(lowerText)) {
2959
+ action = lowerText.includes("session") || lowerText.includes("key") ? "check_status" : "check_balance";
2960
+ confidence = 0.9;
2961
+ return { action, confidence, rawText: text };
2962
+ }
2963
+ if (this.containsTopUpKeywords(lowerText)) {
2964
+ action = "topup";
2965
+ confidence = 0.8;
2966
+ const amount = this.extractAmount(lowerText);
2967
+ return { action, amount, confidence, rawText: text };
2968
+ }
2969
+ if (this.containsRevokeKeywords(lowerText)) {
2970
+ action = "revoke";
2971
+ confidence = 0.9;
2972
+ return { action, confidence, rawText: text };
2973
+ }
2974
+ if (this.containsAutonomyKeywords(lowerText)) {
2975
+ action = "enable_autonomy";
2976
+ confidence = 0.8;
2977
+ const amount = this.extractAmount(lowerText);
2978
+ return { action, amount, confidence, rawText: text, description: "Autonomous delegate limit" };
2979
+ }
2980
+ return null;
2981
+ }
2982
+ /**
2983
+ * Generate system prompt for AI models
2984
+ */
2985
+ static generateSystemPrompt(capabilities = {}) {
2986
+ const enabledFeatures = Object.entries({
2987
+ createPayment: capabilities.createPayment !== false,
2988
+ createSessionKey: capabilities.createSessionKey !== false,
2989
+ checkBalance: capabilities.checkBalance !== false,
2990
+ checkStatus: capabilities.checkStatus !== false,
2991
+ topUpSession: capabilities.topUpSession !== false,
2992
+ revokeSession: capabilities.revokeSession !== false,
2993
+ enableAutonomy: capabilities.enableAutonomy !== false
2994
+ }).filter(([_, enabled]) => enabled).map(([feature]) => feature);
2995
+ return `You are a ZendFi payment assistant. You can help users with crypto payments on Solana.
2996
+
2997
+ Available Actions:
2998
+ ${enabledFeatures.includes("createPayment") ? '- Make payments (e.g., "Buy coffee for $5", "Send $20 to merchant")' : ""}
2999
+ ${enabledFeatures.includes("createSessionKey") ? '- Create session keys (e.g., "Create a session key with $100 budget")' : ""}
3000
+ ${enabledFeatures.includes("checkBalance") ? `- Check balances (e.g., "What's my balance?", "How much do I have?")` : ""}
3001
+ ${enabledFeatures.includes("checkStatus") ? '- Check session status (e.g., "Session key status", "How much is left?")' : ""}
3002
+ ${enabledFeatures.includes("topUpSession") ? '- Top up session keys (e.g., "Add $50 to session key", "Top up with $100")' : ""}
3003
+ ${enabledFeatures.includes("revokeSession") ? '- Revoke session keys (e.g., "Revoke session key", "Cancel my session")' : ""}
3004
+ ${enabledFeatures.includes("enableAutonomy") ? '- Enable autonomous mode (e.g., "Enable auto-pay with $25 limit")' : ""}
3005
+
3006
+ Response Format:
3007
+ Always respond with valid JSON:
3008
+ {
3009
+ "action": "payment" | "create_session" | "check_balance" | "check_status" | "topup" | "revoke" | "enable_autonomy" | "chat_only",
3010
+ "amount_usd": <number if applicable>,
3011
+ "description": "<description>",
3012
+ "message": "<friendly response to user>"
3013
+ }
3014
+
3015
+ Examples:
3016
+ User: "Buy coffee for $5"
3017
+ You: {"action": "payment", "amount_usd": 5, "description": "coffee", "message": "Sure! Processing your $5 coffee payment..."}
3018
+
3019
+ User: "Create a session key with $100"
3020
+ You: {"action": "create_session", "amount_usd": 100, "message": "I'll create a session key with a $100 budget..."}
3021
+
3022
+ User: "What's my balance?"
3023
+ You: {"action": "check_balance", "message": "Let me check your balance..."}
3024
+
3025
+ Be helpful, concise, and always respond in valid JSON format.`;
3026
+ }
3027
+ // ============================================
3028
+ // Keyword Detection
3029
+ // ============================================
3030
+ static containsPaymentKeywords(text) {
3031
+ const keywords = [
3032
+ "buy",
3033
+ "purchase",
3034
+ "pay",
3035
+ "send",
3036
+ "transfer",
3037
+ "payment",
3038
+ "order",
3039
+ "checkout",
3040
+ "subscribe",
3041
+ "donate",
3042
+ "tip"
3043
+ ];
3044
+ return keywords.some((kw) => text.includes(kw));
3045
+ }
3046
+ static containsSessionKeywords(text) {
3047
+ const keywords = [
3048
+ "create session",
3049
+ "new session",
3050
+ "session key",
3051
+ "setup session",
3052
+ "generate session",
3053
+ "make session",
3054
+ "start session"
3055
+ ];
3056
+ return keywords.some((kw) => text.includes(kw));
3057
+ }
3058
+ static containsStatusKeywords(text) {
3059
+ const keywords = [
3060
+ "status",
3061
+ "balance",
3062
+ "remaining",
3063
+ "left",
3064
+ "how much",
3065
+ "check",
3066
+ "show",
3067
+ "view",
3068
+ "display"
3069
+ ];
3070
+ return keywords.some((kw) => text.includes(kw));
3071
+ }
3072
+ static containsTopUpKeywords(text) {
3073
+ const keywords = [
3074
+ "top up",
3075
+ "topup",
3076
+ "add funds",
3077
+ "add money",
3078
+ "fund",
3079
+ "increase",
3080
+ "reload",
3081
+ "refill"
3082
+ ];
3083
+ return keywords.some((kw) => text.includes(kw));
3084
+ }
3085
+ static containsRevokeKeywords(text) {
3086
+ const keywords = [
3087
+ "revoke",
3088
+ "cancel",
3089
+ "disable",
3090
+ "remove",
3091
+ "delete",
3092
+ "stop",
3093
+ "end",
3094
+ "terminate"
3095
+ ];
3096
+ return keywords.some((kw) => text.includes(kw));
3097
+ }
3098
+ static containsAutonomyKeywords(text) {
3099
+ const keywords = [
3100
+ "autonomous",
3101
+ "auto",
3102
+ "automatic",
3103
+ "delegate",
3104
+ "enable auto",
3105
+ "auto-sign",
3106
+ "auto sign",
3107
+ "auto-pay",
3108
+ "auto pay"
3109
+ ];
3110
+ return keywords.some((kw) => text.includes(kw));
3111
+ }
3112
+ // ============================================
3113
+ // Amount Extraction
3114
+ // ============================================
3115
+ static extractAmount(text) {
3116
+ const dollarMatch = text.match(/\$\s*(\d+(?:\.\d{1,2})?)/);
3117
+ if (dollarMatch) {
3118
+ return parseFloat(dollarMatch[1]);
3119
+ }
3120
+ const usdMatch = text.match(/(\d+(?:\.\d{1,2})?)\s*(?:usd|usdc|dollars?)/i);
3121
+ if (usdMatch) {
3122
+ return parseFloat(usdMatch[1]);
3123
+ }
3124
+ const numberMatch = text.match(/(\d+(?:\.\d{1,2})?)/);
3125
+ if (numberMatch) {
3126
+ const num = parseFloat(numberMatch[1]);
3127
+ if (num > 0 && num < 1e5) {
3128
+ return num;
3129
+ }
3130
+ }
3131
+ return void 0;
3132
+ }
3133
+ // ============================================
3134
+ // Description Extraction
3135
+ // ============================================
3136
+ static extractDescription(text) {
3137
+ const lowerText = text.toLowerCase();
3138
+ const items = {
3139
+ "coffee": ["coffee", "espresso", "latte", "cappuccino"],
3140
+ "food": ["food", "meal", "lunch", "dinner", "breakfast"],
3141
+ "drink": ["drink", "beverage", "soda", "juice"],
3142
+ "book": ["book", "ebook", "magazine"],
3143
+ "subscription": ["subscription", "membership", "plan"],
3144
+ "tip": ["tip", "gratuity"],
3145
+ "donation": ["donate", "donation", "contribute"],
3146
+ "game": ["game", "gaming"],
3147
+ "music": ["music", "song", "album"],
3148
+ "video": ["video", "movie", "film"]
3149
+ };
3150
+ for (const [item, keywords] of Object.entries(items)) {
3151
+ if (keywords.some((kw) => lowerText.includes(kw))) {
3152
+ return item;
3153
+ }
3154
+ }
3155
+ const forMatch = text.match(/for\s+(.+?)(?:\s+\$|\s+usd|$)/i);
3156
+ if (forMatch && forMatch[1]) {
3157
+ const desc = forMatch[1].trim();
3158
+ if (desc.length > 0 && desc.length < 50) {
3159
+ return desc;
3160
+ }
3161
+ }
3162
+ return void 0;
3163
+ }
3164
+ };
3165
+ var OpenAIAdapter = class {
3166
+ constructor(apiKey, model = "gpt-4o-mini", capabilities) {
3167
+ this.apiKey = apiKey;
3168
+ this.model = model;
3169
+ this.capabilities = capabilities;
3170
+ }
3171
+ async chat(message, conversationHistory = []) {
3172
+ const systemPrompt = PaymentIntentParser.generateSystemPrompt(this.capabilities);
3173
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
3174
+ method: "POST",
3175
+ headers: {
3176
+ "Authorization": `Bearer ${this.apiKey}`,
3177
+ "Content-Type": "application/json"
3178
+ },
3179
+ body: JSON.stringify({
3180
+ model: this.model,
3181
+ messages: [
3182
+ { role: "system", content: systemPrompt },
3183
+ ...conversationHistory,
3184
+ { role: "user", content: message }
3185
+ ],
3186
+ temperature: 0.7,
3187
+ max_tokens: 500
3188
+ })
3189
+ });
3190
+ if (!response.ok) {
3191
+ throw new Error(`OpenAI API error: ${response.status} ${response.statusText}`);
3192
+ }
3193
+ const data = await response.json();
3194
+ const text = data.choices[0]?.message?.content || "";
3195
+ let intent = null;
3196
+ try {
3197
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
3198
+ if (jsonMatch) {
3199
+ const parsed = JSON.parse(jsonMatch[0]);
3200
+ intent = {
3201
+ action: parsed.action || "chat_only",
3202
+ amount: parsed.amount_usd,
3203
+ description: parsed.description,
3204
+ confidence: 0.9,
3205
+ rawText: text,
3206
+ metadata: { message: parsed.message }
3207
+ };
3208
+ }
3209
+ } catch {
3210
+ intent = PaymentIntentParser.parse(text);
3211
+ }
3212
+ return { text, intent, raw: data };
3213
+ }
3214
+ };
3215
+ var AnthropicAdapter = class {
3216
+ constructor(apiKey, model = "claude-3-5-sonnet-20241022", capabilities) {
3217
+ this.apiKey = apiKey;
3218
+ this.model = model;
3219
+ this.capabilities = capabilities;
3220
+ }
3221
+ async chat(message, conversationHistory = []) {
3222
+ const systemPrompt = PaymentIntentParser.generateSystemPrompt(this.capabilities);
3223
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
3224
+ method: "POST",
3225
+ headers: {
3226
+ "x-api-key": this.apiKey,
3227
+ "anthropic-version": "2023-06-01",
3228
+ "Content-Type": "application/json"
3229
+ },
3230
+ body: JSON.stringify({
3231
+ model: this.model,
3232
+ system: systemPrompt,
3233
+ messages: [
3234
+ ...conversationHistory.map((msg) => ({
3235
+ role: msg.role === "user" ? "user" : "assistant",
3236
+ content: msg.content
3237
+ })),
3238
+ { role: "user", content: message }
3239
+ ],
3240
+ max_tokens: 500
3241
+ })
3242
+ });
3243
+ if (!response.ok) {
3244
+ throw new Error(`Anthropic API error: ${response.status} ${response.statusText}`);
3245
+ }
3246
+ const data = await response.json();
3247
+ const text = data.content[0]?.text || "";
3248
+ let intent = null;
3249
+ try {
3250
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
3251
+ if (jsonMatch) {
3252
+ const parsed = JSON.parse(jsonMatch[0]);
3253
+ intent = {
3254
+ action: parsed.action || "chat_only",
3255
+ amount: parsed.amount_usd,
3256
+ description: parsed.description,
3257
+ confidence: 0.9,
3258
+ rawText: text,
3259
+ metadata: { message: parsed.message }
3260
+ };
3261
+ }
3262
+ } catch {
3263
+ intent = PaymentIntentParser.parse(text);
3264
+ }
3265
+ return { text, intent, raw: data };
3266
+ }
3267
+ };
3268
+ var GeminiAdapter = class {
3269
+ constructor(apiKey, model = "gemini-2.0-flash-exp", capabilities) {
3270
+ this.apiKey = apiKey;
3271
+ this.model = model;
3272
+ this.capabilities = capabilities;
3273
+ }
3274
+ async chat(message) {
3275
+ const systemPrompt = PaymentIntentParser.generateSystemPrompt(this.capabilities);
3276
+ const fullPrompt = `${systemPrompt}
3277
+
3278
+ User: ${message}`;
3279
+ const response = await fetch(
3280
+ `https://generativelanguage.googleapis.com/v1beta/models/${this.model}:generateContent?key=${this.apiKey}`,
3281
+ {
3282
+ method: "POST",
3283
+ headers: { "Content-Type": "application/json" },
3284
+ body: JSON.stringify({
3285
+ contents: [{ parts: [{ text: fullPrompt }] }]
3286
+ })
3287
+ }
3288
+ );
3289
+ if (!response.ok) {
3290
+ throw new Error(`Gemini API error: ${response.status} ${response.statusText}`);
3291
+ }
3292
+ const data = await response.json();
3293
+ const text = data.candidates?.[0]?.content?.parts?.[0]?.text || "";
3294
+ let intent = null;
3295
+ try {
3296
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
3297
+ if (jsonMatch) {
3298
+ const parsed = JSON.parse(jsonMatch[0]);
3299
+ intent = {
3300
+ action: parsed.action || "chat_only",
3301
+ amount: parsed.amount_usd,
3302
+ description: parsed.description,
3303
+ confidence: 0.9,
3304
+ rawText: text,
3305
+ metadata: { message: parsed.message }
3306
+ };
3307
+ }
3308
+ } catch {
3309
+ intent = PaymentIntentParser.parse(text);
3310
+ }
3311
+ return { text, intent, raw: data };
3312
+ }
3313
+ };
3314
+
3315
+ // src/helpers/wallet.ts
3316
+ var WalletConnector = class _WalletConnector {
3317
+ static connectedWallet = null;
3318
+ /**
3319
+ * Detect and connect to a Solana wallet
3320
+ */
3321
+ static async detectAndConnect(config = {}) {
3322
+ if (this.connectedWallet && this.connectedWallet.isConnected()) {
3323
+ return this.connectedWallet;
3324
+ }
3325
+ const detected = this.detectWallets();
3326
+ if (detected.length === 0) {
3327
+ if (config.showInstallPrompt !== false) {
3328
+ this.showInstallPrompt();
3329
+ }
3330
+ throw new Error("No Solana wallet detected. Please install Phantom, Solflare, or Backpack.");
3331
+ }
3332
+ let selectedProvider = detected[0];
3333
+ if (config.preferredProvider && detected.includes(config.preferredProvider)) {
3334
+ selectedProvider = config.preferredProvider;
3335
+ }
3336
+ const wallet = await this.connectToProvider(selectedProvider);
3337
+ this.connectedWallet = wallet;
3338
+ return wallet;
3339
+ }
3340
+ /**
3341
+ * Detect available Solana wallets
3342
+ */
3343
+ static detectWallets() {
3344
+ const detected = [];
3345
+ if (typeof window === "undefined") return detected;
3346
+ if (window.solana?.isPhantom) detected.push("phantom");
3347
+ if (window.solflare?.isSolflare) detected.push("solflare");
3348
+ if (window.backpack?.isBackpack) detected.push("backpack");
3349
+ if (window.coinbaseSolana) detected.push("coinbase");
3350
+ if (window.trustwallet?.solana) detected.push("trust");
3351
+ return detected;
3352
+ }
3353
+ /**
3354
+ * Connect to a specific wallet provider
3355
+ */
3356
+ static async connectToProvider(provider) {
3357
+ if (typeof window === "undefined") {
3358
+ throw new Error("Wallet connection only works in browser environment");
3359
+ }
3360
+ let adapter;
3361
+ switch (provider) {
3362
+ case "phantom":
3363
+ adapter = window.solana;
3364
+ if (!adapter?.isPhantom) {
3365
+ throw new Error("Phantom wallet not found");
3366
+ }
3367
+ break;
3368
+ case "solflare":
3369
+ adapter = window.solflare;
3370
+ if (!adapter?.isSolflare) {
3371
+ throw new Error("Solflare wallet not found");
3372
+ }
3373
+ break;
3374
+ case "backpack":
3375
+ adapter = window.backpack;
3376
+ if (!adapter?.isBackpack) {
3377
+ throw new Error("Backpack wallet not found");
3378
+ }
3379
+ break;
3380
+ case "coinbase":
3381
+ adapter = window.coinbaseSolana;
3382
+ if (!adapter) {
3383
+ throw new Error("Coinbase Wallet not found");
3384
+ }
3385
+ break;
3386
+ case "trust":
3387
+ adapter = window.trustwallet?.solana;
3388
+ if (!adapter) {
3389
+ throw new Error("Trust Wallet not found");
3390
+ }
3391
+ break;
3392
+ default:
3393
+ throw new Error(`Unknown wallet provider: ${provider}`);
3394
+ }
3395
+ try {
3396
+ const response = await adapter.connect();
3397
+ const publicKey = response.publicKey || adapter.publicKey;
3398
+ if (!publicKey) {
3399
+ throw new Error("Failed to get wallet public key");
3400
+ }
3401
+ const connectedWallet = {
3402
+ address: publicKey.toString(),
3403
+ provider,
3404
+ publicKey,
3405
+ signTransaction: async (tx) => {
3406
+ return await adapter.signTransaction(tx);
3407
+ },
3408
+ signAllTransactions: async (txs) => {
3409
+ if (adapter.signAllTransactions) {
3410
+ return await adapter.signAllTransactions(txs);
3411
+ }
3412
+ const signed = [];
3413
+ for (const tx of txs) {
3414
+ signed.push(await adapter.signTransaction(tx));
3415
+ }
3416
+ return signed;
3417
+ },
3418
+ signMessage: async (message) => {
3419
+ if (adapter.signMessage) {
3420
+ return await adapter.signMessage(message);
3421
+ }
3422
+ throw new Error(`${provider} does not support message signing`);
3423
+ },
3424
+ disconnect: async () => {
3425
+ if (adapter.disconnect) {
3426
+ await adapter.disconnect();
3427
+ }
3428
+ _WalletConnector.connectedWallet = null;
3429
+ },
3430
+ isConnected: () => {
3431
+ return adapter.isConnected ?? false;
3432
+ },
3433
+ raw: adapter
3434
+ };
3435
+ return connectedWallet;
3436
+ } catch (error) {
3437
+ throw new Error(`Failed to connect to ${provider}: ${error.message}`);
3438
+ }
3439
+ }
3440
+ /**
3441
+ * Sign and submit a transaction
3442
+ */
3443
+ static async signAndSubmit(transaction, wallet, connection) {
3444
+ const signedTx = await wallet.signTransaction(transaction);
3445
+ const signature = await connection.sendRawTransaction(signedTx.serialize(), {
3446
+ skipPreflight: false,
3447
+ preflightCommitment: "confirmed"
3448
+ });
3449
+ return { signature };
3450
+ }
3451
+ /**
3452
+ * Get current connected wallet
3453
+ */
3454
+ static getConnectedWallet() {
3455
+ return this.connectedWallet;
3456
+ }
3457
+ /**
3458
+ * Disconnect current wallet
3459
+ */
3460
+ static async disconnect() {
3461
+ if (this.connectedWallet) {
3462
+ await this.connectedWallet.disconnect();
3463
+ this.connectedWallet = null;
3464
+ }
3465
+ }
3466
+ /**
3467
+ * Listen for wallet connection changes
3468
+ */
3469
+ static onAccountChange(callback) {
3470
+ if (typeof window === "undefined") {
3471
+ return () => {
3472
+ };
3473
+ }
3474
+ const cleanupFns = [];
3475
+ if (window.solana?.on) {
3476
+ window.solana.on("accountChanged", callback);
3477
+ cleanupFns.push(() => {
3478
+ window.solana?.removeListener("accountChanged", callback);
3479
+ });
3480
+ }
3481
+ if (window.solflare?.on) {
3482
+ window.solflare.on("accountChanged", callback);
3483
+ cleanupFns.push(() => {
3484
+ window.solflare?.removeListener("accountChanged", callback);
3485
+ });
3486
+ }
3487
+ return () => {
3488
+ cleanupFns.forEach((fn) => fn());
3489
+ };
3490
+ }
3491
+ /**
3492
+ * Listen for wallet disconnection
3493
+ */
3494
+ static onDisconnect(callback) {
3495
+ if (typeof window === "undefined") {
3496
+ return () => {
3497
+ };
3498
+ }
3499
+ const cleanupFns = [];
3500
+ if (window.solana?.on) {
3501
+ window.solana.on("disconnect", callback);
3502
+ cleanupFns.push(() => {
3503
+ window.solana?.removeListener("disconnect", callback);
3504
+ });
3505
+ }
3506
+ if (window.solflare?.on) {
3507
+ window.solflare.on("disconnect", callback);
3508
+ cleanupFns.push(() => {
3509
+ window.solflare?.removeListener("disconnect", callback);
3510
+ });
3511
+ }
3512
+ return () => {
3513
+ cleanupFns.forEach((fn) => fn());
3514
+ };
3515
+ }
3516
+ /**
3517
+ * Show install prompt UI
3518
+ */
3519
+ static showInstallPrompt() {
3520
+ const message = `
3521
+ No Solana wallet detected!
3522
+
3523
+ Install one of these wallets:
3524
+ \u2022 Phantom: https://phantom.app
3525
+ \u2022 Solflare: https://solflare.com
3526
+ \u2022 Backpack: https://backpack.app
3527
+ `.trim();
3528
+ console.warn(message);
3529
+ if (typeof window !== "undefined") {
3530
+ const userChoice = window.confirm(
3531
+ "No Solana wallet detected.\n\nWould you like to install Phantom wallet?"
3532
+ );
3533
+ if (userChoice) {
3534
+ window.open("https://phantom.app", "_blank");
3535
+ }
3536
+ }
3537
+ }
3538
+ /**
3539
+ * Check if wallet is installed
3540
+ */
3541
+ static isWalletInstalled(provider) {
3542
+ return this.detectWallets().includes(provider);
3543
+ }
3544
+ /**
3545
+ * Get wallet download URL
3546
+ */
3547
+ static getWalletUrl(provider) {
3548
+ const urls = {
3549
+ phantom: "https://phantom.app",
3550
+ solflare: "https://solflare.com",
3551
+ backpack: "https://backpack.app",
3552
+ coinbase: "https://www.coinbase.com/wallet",
3553
+ trust: "https://trustwallet.com"
3554
+ };
3555
+ return urls[provider];
3556
+ }
3557
+ };
3558
+ function createWalletHook() {
3559
+ let useState;
3560
+ let useEffect;
3561
+ try {
3562
+ const React = __require("react");
3563
+ useState = React.useState;
3564
+ useEffect = React.useEffect;
3565
+ } catch {
3566
+ throw new Error("React not found. Install react to use wallet hooks.");
3567
+ }
3568
+ return function useWallet() {
3569
+ const [wallet, setWallet] = useState(null);
3570
+ const [connecting, setConnecting] = useState(false);
3571
+ const [error, setError] = useState(null);
3572
+ const connect = async (config) => {
3573
+ setConnecting(true);
3574
+ setError(null);
3575
+ try {
3576
+ const connected = await WalletConnector.detectAndConnect(config);
3577
+ setWallet(connected);
3578
+ } catch (err) {
3579
+ setError(err);
3580
+ } finally {
3581
+ setConnecting(false);
3582
+ }
3583
+ };
3584
+ const disconnect = async () => {
3585
+ await WalletConnector.disconnect();
3586
+ setWallet(null);
3587
+ };
3588
+ useEffect(() => {
3589
+ const cleanup = WalletConnector.onAccountChange((publicKey) => {
3590
+ if (wallet) {
3591
+ setWallet({ ...wallet, publicKey, address: publicKey.toString() });
3592
+ }
3593
+ });
3594
+ return cleanup;
3595
+ }, [wallet]);
3596
+ return {
3597
+ wallet,
3598
+ connecting,
3599
+ error,
3600
+ connect,
3601
+ disconnect,
3602
+ isConnected: wallet !== null
3603
+ };
3604
+ };
3605
+ }
3606
+
3607
+ // src/helpers/security.ts
3608
+ var PINValidator = class {
3609
+ /**
3610
+ * Validate PIN strength and format
3611
+ */
3612
+ static validate(pin) {
3613
+ const errors = [];
3614
+ const suggestions = [];
3615
+ if (!pin || pin.length < 4) {
3616
+ errors.push("PIN must be at least 4 digits");
3617
+ }
3618
+ if (pin.length > 12) {
3619
+ errors.push("PIN must be at most 12 digits");
3620
+ }
3621
+ if (!/^\d+$/.test(pin)) {
3622
+ errors.push("PIN must contain only digits");
3623
+ }
3624
+ if (this.isWeakPattern(pin)) {
3625
+ errors.push("PIN is too simple");
3626
+ suggestions.push("Avoid sequential numbers (1234) or repeated digits (1111)");
3627
+ }
3628
+ let strength = "strong";
3629
+ if (errors.length > 0) {
3630
+ strength = "weak";
3631
+ } else if (pin.length <= 4 || this.hasRepeatedDigits(pin)) {
3632
+ strength = "weak";
3633
+ suggestions.push("Use at least 6 digits for better security");
3634
+ } else if (pin.length <= 6) {
3635
+ strength = "medium";
3636
+ suggestions.push("Use 8+ digits for maximum security");
3637
+ }
3638
+ return {
3639
+ valid: errors.length === 0,
3640
+ strength,
3641
+ errors,
3642
+ suggestions
3643
+ };
3644
+ }
3645
+ /**
3646
+ * Check PIN strength (0-100 score)
3647
+ */
3648
+ static strengthScore(pin) {
3649
+ if (!pin) return 0;
3650
+ let score = 0;
3651
+ score += Math.min(pin.length * 10, 50);
3652
+ const uniqueDigits = new Set(pin).size;
3653
+ score += uniqueDigits * 5;
3654
+ if (!this.isSequential(pin)) {
3655
+ score += 20;
3656
+ }
3657
+ if (!this.hasRepeatedDigits(pin)) {
3658
+ score += 10;
3659
+ }
3660
+ return Math.min(score, 100);
3661
+ }
3662
+ /**
3663
+ * Generate a secure random PIN
3664
+ */
3665
+ static generateSecureRandom(length = 6) {
3666
+ if (length < 4 || length > 12) {
3667
+ throw new Error("PIN length must be between 4 and 12");
3668
+ }
3669
+ const array = new Uint32Array(length);
3670
+ crypto.getRandomValues(array);
3671
+ const pin = Array.from(array).map((num) => num % 10).join("");
3672
+ if (this.isWeakPattern(pin)) {
3673
+ return this.generateSecureRandom(length);
3674
+ }
3675
+ return pin;
3676
+ }
3677
+ /**
3678
+ * Check for weak patterns
3679
+ */
3680
+ static isWeakPattern(pin) {
3681
+ if (this.isSequential(pin)) return true;
3682
+ if (/^(\d)\1+$/.test(pin)) return true;
3683
+ const commonPatterns = [
3684
+ "1234",
3685
+ "4321",
3686
+ "1111",
3687
+ "2222",
3688
+ "3333",
3689
+ "4444",
3690
+ "5555",
3691
+ "6666",
3692
+ "7777",
3693
+ "8888",
3694
+ "9999",
3695
+ "0000",
3696
+ "1212",
3697
+ "2323",
3698
+ "0123",
3699
+ "3210",
3700
+ "9876",
3701
+ "6789"
3702
+ ];
3703
+ return commonPatterns.some((pattern) => pin.includes(pattern));
3704
+ }
3705
+ /**
3706
+ * Check if PIN is sequential
3707
+ */
3708
+ static isSequential(pin) {
3709
+ for (let i = 1; i < pin.length; i++) {
3710
+ const diff = parseInt(pin[i]) - parseInt(pin[i - 1]);
3711
+ if (Math.abs(diff) !== 1) return false;
3712
+ }
3713
+ return true;
3714
+ }
3715
+ /**
3716
+ * Check if PIN has repeated digits
3717
+ */
3718
+ static hasRepeatedDigits(pin) {
3719
+ return /(\d)\1{2,}/.test(pin);
3720
+ }
3721
+ /**
3722
+ * Hash PIN for storage (if needed)
3723
+ * Note: For device-bound keys, the PIN is used for key derivation, not storage
3724
+ */
3725
+ static async hashPIN(pin, salt) {
3726
+ const actualSalt = salt || crypto.getRandomValues(new Uint8Array(16));
3727
+ const encoder = new TextEncoder();
3728
+ const data = encoder.encode(pin + actualSalt);
3729
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
3730
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
3731
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
3732
+ }
3733
+ };
3734
+ var PINRateLimiter = class {
3735
+ attempts = /* @__PURE__ */ new Map();
3736
+ locked = /* @__PURE__ */ new Map();
3737
+ maxAttempts;
3738
+ windowMs;
3739
+ lockoutMs;
3740
+ constructor(config = {}) {
3741
+ this.maxAttempts = config.maxAttempts || 3;
3742
+ this.windowMs = config.windowMs || 6e4;
3743
+ this.lockoutMs = config.lockoutMs || 3e5;
3744
+ }
3745
+ /**
3746
+ * Check if attempt is allowed
3747
+ */
3748
+ async checkAttempt(sessionKeyId) {
3749
+ const now = Date.now();
3750
+ const lockoutUntil = this.locked.get(sessionKeyId);
3751
+ if (lockoutUntil && now < lockoutUntil) {
3752
+ const lockoutSeconds = Math.ceil((lockoutUntil - now) / 1e3);
3753
+ return {
3754
+ allowed: false,
3755
+ remainingAttempts: 0,
3756
+ lockoutSeconds
3757
+ };
3758
+ }
3759
+ if (lockoutUntil && now >= lockoutUntil) {
3760
+ this.locked.delete(sessionKeyId);
3761
+ this.attempts.delete(sessionKeyId);
3762
+ }
3763
+ const recentAttempts = this.getRecentAttempts(sessionKeyId, now);
3764
+ const remainingAttempts = this.maxAttempts - recentAttempts.length;
3765
+ if (remainingAttempts <= 0) {
3766
+ this.locked.set(sessionKeyId, now + this.lockoutMs);
3767
+ return {
3768
+ allowed: false,
3769
+ remainingAttempts: 0,
3770
+ lockoutSeconds: Math.ceil(this.lockoutMs / 1e3)
3771
+ };
3772
+ }
3773
+ return {
3774
+ allowed: true,
3775
+ remainingAttempts
3776
+ };
3777
+ }
3778
+ /**
3779
+ * Record a failed attempt
3780
+ */
3781
+ recordFailedAttempt(sessionKeyId) {
3782
+ const now = Date.now();
3783
+ const attempts = this.attempts.get(sessionKeyId) || [];
3784
+ attempts.push(now);
3785
+ this.attempts.set(sessionKeyId, attempts);
3786
+ }
3787
+ /**
3788
+ * Record a successful attempt (clears history)
3789
+ */
3790
+ recordSuccessfulAttempt(sessionKeyId) {
3791
+ this.attempts.delete(sessionKeyId);
3792
+ this.locked.delete(sessionKeyId);
3793
+ }
3794
+ /**
3795
+ * Get recent attempts within window
3796
+ */
3797
+ getRecentAttempts(sessionKeyId, now) {
3798
+ const attempts = this.attempts.get(sessionKeyId) || [];
3799
+ const cutoff = now - this.windowMs;
3800
+ const recent = attempts.filter((timestamp) => timestamp > cutoff);
3801
+ if (recent.length !== attempts.length) {
3802
+ this.attempts.set(sessionKeyId, recent);
3803
+ }
3804
+ return recent;
3805
+ }
3806
+ /**
3807
+ * Clear all rate limit data
3808
+ */
3809
+ clear() {
3810
+ this.attempts.clear();
3811
+ this.locked.clear();
3812
+ }
3813
+ /**
3814
+ * Check lockout status
3815
+ */
3816
+ isLockedOut(sessionKeyId) {
3817
+ const lockoutUntil = this.locked.get(sessionKeyId);
3818
+ return lockoutUntil ? Date.now() < lockoutUntil : false;
3819
+ }
3820
+ /**
3821
+ * Get remaining lockout time
3822
+ */
3823
+ getRemainingLockoutTime(sessionKeyId) {
3824
+ const lockoutUntil = this.locked.get(sessionKeyId);
3825
+ if (!lockoutUntil) return 0;
3826
+ return Math.max(0, lockoutUntil - Date.now());
3827
+ }
3828
+ };
3829
+ var SecureStorage = class {
3830
+ /**
3831
+ * Store data with encryption (basic)
3832
+ * For production, consider using Web Crypto API with user-derived keys
3833
+ */
3834
+ static async setEncrypted(key, value, secret) {
3835
+ const encrypted = await this.encrypt(value, secret);
3836
+ localStorage.setItem(key, JSON.stringify(encrypted));
3837
+ }
3838
+ /**
3839
+ * Retrieve encrypted data
3840
+ */
3841
+ static async getEncrypted(key, secret) {
3842
+ const stored = localStorage.getItem(key);
3843
+ if (!stored) return null;
3844
+ try {
3845
+ const encrypted = JSON.parse(stored);
3846
+ return await this.decrypt(encrypted, secret);
3847
+ } catch {
3848
+ return null;
3849
+ }
3850
+ }
3851
+ /**
3852
+ * Encrypt string with AES-GCM
3853
+ */
3854
+ static async encrypt(plaintext, secret) {
3855
+ const encoder = new TextEncoder();
3856
+ const data = encoder.encode(plaintext);
3857
+ const key = await this.deriveKey(secret);
3858
+ const iv = crypto.getRandomValues(new Uint8Array(12));
3859
+ const encrypted = await crypto.subtle.encrypt(
3860
+ { name: "AES-GCM", iv },
3861
+ key,
3862
+ data
3863
+ );
3864
+ return {
3865
+ ciphertext: btoa(String.fromCharCode(...new Uint8Array(encrypted))),
3866
+ iv: btoa(String.fromCharCode(...iv))
3867
+ };
3868
+ }
3869
+ /**
3870
+ * Decrypt AES-GCM ciphertext
3871
+ */
3872
+ static async decrypt(encrypted, secret) {
3873
+ const key = await this.deriveKey(secret);
3874
+ const iv = Uint8Array.from(atob(encrypted.iv), (c) => c.charCodeAt(0));
3875
+ const ciphertext = Uint8Array.from(atob(encrypted.ciphertext), (c) => c.charCodeAt(0));
3876
+ const decrypted = await crypto.subtle.decrypt(
3877
+ { name: "AES-GCM", iv },
3878
+ key,
3879
+ ciphertext
3880
+ );
3881
+ const decoder = new TextDecoder();
3882
+ return decoder.decode(decrypted);
3883
+ }
3884
+ /**
3885
+ * Derive encryption key from secret
3886
+ */
3887
+ static async deriveKey(secret) {
3888
+ const encoder = new TextEncoder();
3889
+ const keyMaterial = await crypto.subtle.importKey(
3890
+ "raw",
3891
+ encoder.encode(secret),
3892
+ { name: "PBKDF2" },
3893
+ false,
3894
+ ["deriveBits", "deriveKey"]
3895
+ );
3896
+ return await crypto.subtle.deriveKey(
3897
+ {
3898
+ name: "PBKDF2",
3899
+ salt: encoder.encode("zendfi-secure-storage"),
3900
+ iterations: 1e5,
3901
+ hash: "SHA-256"
3902
+ },
3903
+ keyMaterial,
3904
+ { name: "AES-GCM", length: 256 },
3905
+ false,
3906
+ ["encrypt", "decrypt"]
3907
+ );
3908
+ }
3909
+ /**
3910
+ * Clear all secure storage
3911
+ */
3912
+ static clearAll(namespace = "zendfi") {
3913
+ const keysToRemove = [];
3914
+ for (let i = 0; i < localStorage.length; i++) {
3915
+ const key = localStorage.key(i);
3916
+ if (key?.startsWith(namespace)) {
3917
+ keysToRemove.push(key);
3918
+ }
3919
+ }
3920
+ keysToRemove.forEach((key) => localStorage.removeItem(key));
3921
+ }
3922
+ };
3923
+
3924
+ // src/helpers/polling.ts
3925
+ var TransactionPoller = class {
3926
+ /**
3927
+ * Wait for transaction confirmation
3928
+ */
3929
+ static async waitForConfirmation(signature, options = {}) {
3930
+ const {
3931
+ timeout = 6e4,
3932
+ interval = 2e3,
3933
+ maxInterval = 1e4,
3934
+ maxAttempts = 30,
3935
+ commitment = "confirmed",
3936
+ rpcUrl
3937
+ } = options;
3938
+ const startTime = Date.now();
3939
+ let currentInterval = interval;
3940
+ let attempts = 0;
3941
+ while (true) {
3942
+ attempts++;
3943
+ if (Date.now() - startTime > timeout) {
3944
+ return {
3945
+ confirmed: false,
3946
+ signature,
3947
+ error: `Transaction confirmation timeout after ${timeout}ms`
3948
+ };
3949
+ }
3950
+ if (attempts > maxAttempts) {
3951
+ return {
3952
+ confirmed: false,
3953
+ signature,
3954
+ error: `Maximum polling attempts (${maxAttempts}) exceeded`
3955
+ };
3956
+ }
3957
+ try {
3958
+ const status = await this.checkTransactionStatus(signature, commitment, rpcUrl);
3959
+ if (status.confirmed) {
3960
+ return status;
3961
+ }
3962
+ if (status.error) {
3963
+ return status;
3964
+ }
3965
+ await this.sleep(currentInterval);
3966
+ currentInterval = Math.min(currentInterval * 1.5, maxInterval);
3967
+ } catch (error) {
3968
+ await this.sleep(currentInterval);
3969
+ currentInterval = Math.min(currentInterval * 1.5, maxInterval);
3970
+ }
3971
+ }
3972
+ }
3973
+ /**
3974
+ * Check transaction status via RPC
3975
+ */
3976
+ static async checkTransactionStatus(signature, commitment = "confirmed", rpcUrl) {
3977
+ const endpoint = rpcUrl || this.getDefaultRpcUrl();
3978
+ const response = await fetch(endpoint, {
3979
+ method: "POST",
3980
+ headers: { "Content-Type": "application/json" },
3981
+ body: JSON.stringify({
3982
+ jsonrpc: "2.0",
3983
+ id: 1,
3984
+ method: "getSignatureStatuses",
3985
+ params: [[signature], { searchTransactionHistory: true }]
3986
+ })
3987
+ });
3988
+ if (!response.ok) {
3989
+ throw new Error(`RPC error: ${response.status} ${response.statusText}`);
3990
+ }
3991
+ const data = await response.json();
3992
+ if (data.error) {
3993
+ return {
3994
+ confirmed: false,
3995
+ signature,
3996
+ error: data.error.message || "RPC error"
3997
+ };
3998
+ }
3999
+ const status = data.result?.value?.[0];
4000
+ if (!status) {
4001
+ return {
4002
+ confirmed: false,
4003
+ signature
4004
+ };
4005
+ }
4006
+ const isConfirmed = this.isCommitmentReached(status, commitment);
4007
+ return {
4008
+ confirmed: isConfirmed,
4009
+ signature,
4010
+ slot: status.slot,
4011
+ confirmations: status.confirmations,
4012
+ error: status.err ? JSON.stringify(status.err) : void 0
4013
+ };
4014
+ }
4015
+ /**
4016
+ * Check if commitment level is reached
4017
+ */
4018
+ static isCommitmentReached(status, commitment) {
4019
+ if (status.err) return false;
4020
+ switch (commitment) {
4021
+ case "processed":
4022
+ return true;
4023
+ // Any status means processed
4024
+ case "confirmed":
4025
+ return status.confirmationStatus === "confirmed" || status.confirmationStatus === "finalized";
4026
+ case "finalized":
4027
+ return status.confirmationStatus === "finalized";
4028
+ default:
4029
+ return false;
4030
+ }
4031
+ }
4032
+ /**
4033
+ * Get default RPC URL based on environment
4034
+ */
4035
+ static getDefaultRpcUrl() {
4036
+ if (typeof window !== "undefined" && window.location) {
4037
+ const hostname = window.location.hostname;
4038
+ if (hostname.includes("localhost") || hostname.includes("dev")) {
4039
+ return "https://api.devnet.solana.com";
4040
+ }
4041
+ }
4042
+ return "https://api.mainnet-beta.solana.com";
4043
+ }
4044
+ /**
4045
+ * Poll multiple transactions in parallel
4046
+ */
4047
+ static async waitForMultiple(signatures, options = {}) {
4048
+ return await Promise.all(
4049
+ signatures.map((sig) => this.waitForConfirmation(sig, options))
4050
+ );
4051
+ }
4052
+ /**
4053
+ * Get transaction details after confirmation
4054
+ */
4055
+ static async getTransactionDetails(signature, rpcUrl) {
4056
+ const endpoint = rpcUrl || this.getDefaultRpcUrl();
4057
+ const response = await fetch(endpoint, {
4058
+ method: "POST",
4059
+ headers: { "Content-Type": "application/json" },
4060
+ body: JSON.stringify({
4061
+ jsonrpc: "2.0",
4062
+ id: 1,
4063
+ method: "getTransaction",
4064
+ params: [
4065
+ signature,
4066
+ {
4067
+ encoding: "jsonParsed",
4068
+ commitment: "confirmed",
4069
+ maxSupportedTransactionVersion: 0
4070
+ }
4071
+ ]
4072
+ })
4073
+ });
4074
+ if (!response.ok) {
4075
+ throw new Error(`RPC error: ${response.status}`);
4076
+ }
4077
+ const data = await response.json();
4078
+ if (data.error) {
4079
+ throw new Error(data.error.message || "Failed to get transaction");
4080
+ }
4081
+ return data.result;
4082
+ }
4083
+ /**
4084
+ * Check if transaction exists on chain
4085
+ */
4086
+ static async exists(signature, rpcUrl) {
4087
+ try {
4088
+ const status = await this.checkTransactionStatus(signature, "confirmed", rpcUrl);
4089
+ return status.confirmed || !!status.slot;
4090
+ } catch {
4091
+ return false;
4092
+ }
4093
+ }
4094
+ /**
4095
+ * Get recent blockhash (useful for transaction building)
4096
+ */
4097
+ static async getRecentBlockhash(rpcUrl) {
4098
+ const endpoint = rpcUrl || this.getDefaultRpcUrl();
4099
+ const response = await fetch(endpoint, {
4100
+ method: "POST",
4101
+ headers: { "Content-Type": "application/json" },
4102
+ body: JSON.stringify({
4103
+ jsonrpc: "2.0",
4104
+ id: 1,
4105
+ method: "getLatestBlockhash",
4106
+ params: [{ commitment: "finalized" }]
4107
+ })
4108
+ });
4109
+ if (!response.ok) {
4110
+ throw new Error(`RPC error: ${response.status}`);
4111
+ }
4112
+ const data = await response.json();
4113
+ if (data.error) {
4114
+ throw new Error(data.error.message);
4115
+ }
4116
+ return data.result.value;
4117
+ }
4118
+ /**
4119
+ * Sleep utility
4120
+ */
4121
+ static sleep(ms) {
4122
+ return new Promise((resolve) => setTimeout(resolve, ms));
4123
+ }
4124
+ };
4125
+ var TransactionMonitor = class {
4126
+ monitors = /* @__PURE__ */ new Map();
4127
+ /**
4128
+ * Start monitoring a transaction
4129
+ */
4130
+ monitor(signature, callbacks, options = {}) {
4131
+ this.stopMonitoring(signature);
4132
+ const { timeout = 6e4, interval = 2e3 } = options;
4133
+ const startTime = Date.now();
4134
+ const intervalId = setInterval(async () => {
4135
+ if (Date.now() - startTime > timeout) {
4136
+ this.stopMonitoring(signature);
4137
+ callbacks.onTimeout?.();
4138
+ return;
4139
+ }
4140
+ try {
4141
+ const status = await TransactionPoller.waitForConfirmation(signature, {
4142
+ ...options,
4143
+ maxAttempts: 1
4144
+ // Single check per interval
4145
+ });
4146
+ if (status.confirmed) {
4147
+ this.stopMonitoring(signature);
4148
+ callbacks.onConfirmed?.(status);
4149
+ } else if (status.error) {
4150
+ this.stopMonitoring(signature);
4151
+ callbacks.onFailed?.(status);
4152
+ }
4153
+ } catch (error) {
4154
+ }
4155
+ }, interval);
4156
+ this.monitors.set(signature, { interval: intervalId, callbacks });
4157
+ }
4158
+ /**
4159
+ * Stop monitoring a transaction
4160
+ */
4161
+ stopMonitoring(signature) {
4162
+ const monitor = this.monitors.get(signature);
4163
+ if (monitor) {
4164
+ clearInterval(monitor.interval);
4165
+ this.monitors.delete(signature);
4166
+ }
4167
+ }
4168
+ /**
4169
+ * Stop all monitors
4170
+ */
4171
+ stopAll() {
4172
+ for (const [signature] of this.monitors) {
4173
+ this.stopMonitoring(signature);
4174
+ }
4175
+ }
4176
+ /**
4177
+ * Get active monitors
4178
+ */
4179
+ getActiveMonitors() {
4180
+ return Array.from(this.monitors.keys());
4181
+ }
4182
+ };
4183
+
4184
+ // src/helpers/recovery.ts
4185
+ var RetryStrategy = class {
4186
+ /**
4187
+ * Execute function with retry logic
4188
+ */
4189
+ static async withRetry(fn, options = {}) {
4190
+ const {
4191
+ maxAttempts = 3,
4192
+ backoffMs = 1e3,
4193
+ backoffMultiplier = 2,
4194
+ maxBackoffMs = 3e4,
4195
+ shouldRetry = () => true,
4196
+ onRetry
4197
+ } = options;
4198
+ let lastError = null;
4199
+ let currentBackoff = backoffMs;
4200
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
4201
+ try {
4202
+ return await fn();
4203
+ } catch (error) {
4204
+ lastError = error;
4205
+ if (attempt === maxAttempts || !shouldRetry(error, attempt)) {
4206
+ throw error;
4207
+ }
4208
+ const jitter = Math.random() * 0.3 * currentBackoff;
4209
+ const nextDelay = Math.min(currentBackoff + jitter, maxBackoffMs);
4210
+ onRetry?.(error, attempt, nextDelay);
4211
+ await this.sleep(nextDelay);
4212
+ currentBackoff *= backoffMultiplier;
4213
+ }
4214
+ }
4215
+ throw lastError || new Error("Retry failed");
4216
+ }
4217
+ /**
4218
+ * Retry with linear backoff
4219
+ */
4220
+ static async withLinearRetry(fn, maxAttempts = 3, delayMs = 1e3) {
4221
+ return this.withRetry(fn, {
4222
+ maxAttempts,
4223
+ backoffMs: delayMs,
4224
+ backoffMultiplier: 1
4225
+ // Linear
4226
+ });
4227
+ }
4228
+ /**
4229
+ * Retry with custom backoff function
4230
+ */
4231
+ static async withCustomBackoff(fn, backoffFn, maxAttempts = 3) {
4232
+ let lastError = null;
4233
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
4234
+ try {
4235
+ return await fn();
4236
+ } catch (error) {
4237
+ lastError = error;
4238
+ if (attempt === maxAttempts) {
4239
+ throw error;
4240
+ }
4241
+ const delay = backoffFn(attempt);
4242
+ await this.sleep(delay);
4243
+ }
4244
+ }
4245
+ throw lastError || new Error("Retry failed");
4246
+ }
4247
+ /**
4248
+ * Check if error is retryable
4249
+ */
4250
+ static isRetryableError(error) {
4251
+ if (error.name === "NetworkError" || error.message?.includes("network")) {
4252
+ return true;
4253
+ }
4254
+ if (error.name === "TimeoutError" || error.message?.includes("timeout")) {
4255
+ return true;
4256
+ }
4257
+ if (error.status === 429 || error.code === "RATE_LIMIT_EXCEEDED") {
4258
+ return true;
4259
+ }
4260
+ if (error.status >= 500 && error.status < 600) {
4261
+ return true;
4262
+ }
4263
+ if (error.message?.includes("blockhash") || error.message?.includes("recent")) {
4264
+ return true;
4265
+ }
4266
+ return false;
4267
+ }
4268
+ static sleep(ms) {
4269
+ return new Promise((resolve) => setTimeout(resolve, ms));
4270
+ }
4271
+ };
4272
+ var ErrorRecovery = class {
4273
+ /**
4274
+ * Recover from network errors with retry
4275
+ */
4276
+ static async recoverFromNetworkError(fn, maxAttempts = 3) {
4277
+ return RetryStrategy.withRetry(fn, {
4278
+ maxAttempts,
4279
+ backoffMs: 2e3,
4280
+ shouldRetry: (error) => {
4281
+ return error.name === "NetworkError" || error.message?.includes("network") || error.message?.includes("fetch");
4282
+ },
4283
+ onRetry: (error, attempt, nextDelay) => {
4284
+ console.warn(
4285
+ `Network error (attempt ${attempt}), retrying in ${nextDelay}ms:`,
4286
+ error.message
4287
+ );
4288
+ }
4289
+ });
4290
+ }
4291
+ /**
4292
+ * Recover from rate limit errors with exponential backoff
4293
+ */
4294
+ static async recoverFromRateLimit(fn, maxAttempts = 5) {
4295
+ return RetryStrategy.withRetry(fn, {
4296
+ maxAttempts,
4297
+ backoffMs: 5e3,
4298
+ // Start with 5 seconds
4299
+ backoffMultiplier: 2,
4300
+ maxBackoffMs: 6e4,
4301
+ // Cap at 1 minute
4302
+ shouldRetry: (error) => {
4303
+ return error.status === 429 || error.code === "RATE_LIMIT_EXCEEDED";
4304
+ },
4305
+ onRetry: (attempt, nextDelay) => {
4306
+ console.warn(
4307
+ `Rate limited (attempt ${attempt}), waiting ${nextDelay}ms before retry`
4308
+ );
4309
+ }
4310
+ });
4311
+ }
4312
+ /**
4313
+ * Recover from Solana RPC errors (blockhash, etc.)
4314
+ */
4315
+ static async recoverFromRPCError(fn, maxAttempts = 3) {
4316
+ return RetryStrategy.withRetry(fn, {
4317
+ maxAttempts,
4318
+ backoffMs: 1e3,
4319
+ shouldRetry: (error) => {
4320
+ const message = error.message?.toLowerCase() || "";
4321
+ return message.includes("blockhash") || message.includes("recent") || message.includes("slot") || message.includes("rpc");
4322
+ },
4323
+ onRetry: (error, attempt, nextDelay) => {
4324
+ console.warn(
4325
+ `RPC error (attempt ${attempt}), retrying in ${nextDelay}ms:`,
4326
+ error.message
4327
+ );
4328
+ }
4329
+ });
4330
+ }
4331
+ /**
4332
+ * Recover from timeout errors
4333
+ */
4334
+ static async recoverFromTimeout(fn, timeoutMs = 3e4, maxAttempts = 2) {
4335
+ return RetryStrategy.withRetry(
4336
+ () => this.withTimeout(fn, timeoutMs),
4337
+ {
4338
+ maxAttempts,
4339
+ backoffMs: 5e3,
4340
+ shouldRetry: (error) => {
4341
+ return error.name === "TimeoutError" || error.message?.includes("timeout");
4342
+ }
4343
+ }
4344
+ );
4345
+ }
4346
+ /**
4347
+ * Add timeout to async function
4348
+ */
4349
+ static async withTimeout(fn, timeoutMs) {
4350
+ return Promise.race([
4351
+ fn(),
4352
+ new Promise((_, reject) => {
4353
+ setTimeout(() => {
4354
+ reject(new Error(`Operation timed out after ${timeoutMs}ms`));
4355
+ }, timeoutMs);
4356
+ })
4357
+ ]);
4358
+ }
4359
+ /**
4360
+ * Circuit breaker pattern for repeated failures
4361
+ */
4362
+ static createCircuitBreaker(fn, options = {}) {
4363
+ const {
4364
+ failureThreshold = 5,
4365
+ resetTimeoutMs = 6e4,
4366
+ onStateChange
4367
+ } = options;
4368
+ let state = "closed";
4369
+ let failureCount = 0;
4370
+ let lastFailureTime = 0;
4371
+ let resetTimer = null;
4372
+ return async () => {
4373
+ if (state === "open" && Date.now() - lastFailureTime > resetTimeoutMs) {
4374
+ state = "half-open";
4375
+ onStateChange?.("half-open");
4376
+ }
4377
+ if (state === "open") {
4378
+ throw new Error("Circuit breaker is OPEN - too many failures");
4379
+ }
4380
+ try {
4381
+ const result = await fn();
4382
+ if (state === "half-open") {
4383
+ state = "closed";
4384
+ onStateChange?.("closed");
4385
+ }
4386
+ failureCount = 0;
4387
+ return result;
4388
+ } catch (error) {
4389
+ failureCount++;
4390
+ lastFailureTime = Date.now();
4391
+ if (failureCount >= failureThreshold) {
4392
+ state = "open";
4393
+ onStateChange?.("open");
4394
+ if (resetTimer) clearTimeout(resetTimer);
4395
+ resetTimer = setTimeout(() => {
4396
+ state = "half-open";
4397
+ onStateChange?.("half-open");
4398
+ }, resetTimeoutMs);
4399
+ }
4400
+ throw error;
4401
+ }
4402
+ };
4403
+ }
4404
+ /**
4405
+ * Fallback to alternative function on error
4406
+ */
4407
+ static async withFallback(primaryFn, fallbackFn, shouldFallback = () => true) {
4408
+ try {
4409
+ return await primaryFn();
4410
+ } catch (error) {
4411
+ if (shouldFallback(error)) {
4412
+ console.warn("Primary function failed, using fallback:", error.message);
4413
+ return await fallbackFn();
4414
+ }
4415
+ throw error;
4416
+ }
4417
+ }
4418
+ /**
4419
+ * Graceful degradation - return partial result on error
4420
+ */
4421
+ static async withGracefulDegradation(fn, defaultValue, onError) {
4422
+ try {
4423
+ return await fn();
4424
+ } catch (error) {
4425
+ onError?.(error);
4426
+ console.warn("Operation failed, returning default value:", error.message);
4427
+ return defaultValue;
4428
+ }
4429
+ }
4430
+ };
4431
+
4432
+ // src/helpers/lifecycle.ts
4433
+ var SessionKeyLifecycle = class {
4434
+ constructor(client, config = {}) {
4435
+ this.client = client;
4436
+ this.config = config;
4437
+ if (config.autoCleanup && typeof window !== "undefined") {
4438
+ window.addEventListener("beforeunload", () => {
4439
+ this.cleanup().catch(console.error);
4440
+ });
4441
+ }
4442
+ }
4443
+ sessionKeyId = null;
4444
+ sessionWallet = null;
4445
+ encryptedKey = null;
4446
+ deviceFingerprint = null;
4447
+ /**
4448
+ * Create and fund session key in one call
4449
+ * Handles: keypair generation → encryption → backend registration → funding
4450
+ */
4451
+ async createAndFund(config) {
4452
+ const fingerprint = this.config.deviceFingerprintProvider ? await this.config.deviceFingerprintProvider() : await this.generateDeviceFingerprint();
4453
+ this.deviceFingerprint = fingerprint;
4454
+ const pin = this.config.pinProvider ? await this.config.pinProvider() : await this.promptForPIN("Create PIN for session key");
4455
+ const { Keypair } = await this.getSolanaWeb3();
4456
+ const { SessionKeyCrypto: SessionKeyCrypto2 } = await import("./device-bound-crypto-VX7SFVHT.mjs");
4457
+ const keypair = Keypair.generate();
4458
+ const encrypted = await SessionKeyCrypto2.encrypt(
4459
+ keypair.secretKey,
4460
+ pin,
4461
+ fingerprint
4462
+ );
4463
+ this.encryptedKey = {
4464
+ ciphertext: encrypted.encryptedData,
4465
+ nonce: encrypted.nonce
4466
+ };
4467
+ const response = await this.client.sessionKeys.createDeviceBound({
4468
+ user_wallet: config.userWallet,
4469
+ agent_id: config.agentId,
4470
+ agent_name: config.agentName,
4471
+ limit_usdc: config.limitUsdc,
4472
+ duration_days: config.durationDays || 7,
4473
+ encrypted_session_key: encrypted.encryptedData,
4474
+ nonce: encrypted.nonce,
4475
+ session_public_key: keypair.publicKey.toBase58(),
4476
+ device_fingerprint: fingerprint
4477
+ });
4478
+ this.sessionKeyId = response.session_key_id;
4479
+ this.sessionWallet = response.session_wallet;
4480
+ if (this.config.cache) {
4481
+ await this.config.cache.getCached(
4482
+ this.sessionKeyId,
4483
+ async () => keypair,
4484
+ { deviceFingerprint: fingerprint }
4485
+ );
4486
+ }
4487
+ if (config.onApprovalNeeded) {
4488
+ const topupResponse = await this.client.sessionKeys.topUp(
4489
+ this.sessionKeyId,
4490
+ {
4491
+ user_wallet: config.userWallet,
4492
+ amount_usdc: config.limitUsdc,
4493
+ device_fingerprint: fingerprint
4494
+ }
4495
+ );
4496
+ await config.onApprovalNeeded(topupResponse.top_up_transaction);
4497
+ }
4498
+ return {
4499
+ sessionKeyId: this.sessionKeyId,
4500
+ sessionWallet: this.sessionWallet
4501
+ };
4502
+ }
4503
+ /**
4504
+ * Make a payment using the session key
4505
+ * Auto-handles caching, PIN prompts, and signing
4506
+ */
4507
+ async pay(amount, description) {
4508
+ if (!this.sessionKeyId) {
4509
+ throw new Error("No active session key. Call createAndFund() first.");
4510
+ }
4511
+ let keypair;
4512
+ if (this.config.cache) {
4513
+ keypair = await this.config.cache.getCached(
4514
+ this.sessionKeyId,
4515
+ async () => {
4516
+ const pin = this.config.pinProvider ? await this.config.pinProvider() : await this.promptForPIN("Enter PIN to sign payment");
4517
+ return await this.decryptKeypair(pin);
4518
+ },
4519
+ { deviceFingerprint: this.deviceFingerprint || void 0 }
4520
+ );
4521
+ } else {
4522
+ const pin = this.config.pinProvider ? await this.config.pinProvider() : await this.promptForPIN("Enter PIN to sign payment");
4523
+ keypair = await this.decryptKeypair(pin);
4524
+ }
4525
+ const paymentResponse = await this.client.smart.execute({
4526
+ user_wallet: this.sessionWallet,
4527
+ // Session wallet, not user wallet
4528
+ amount_usd: amount,
4529
+ description,
4530
+ agent_id: "session-lifecycle",
4531
+ auto_detect_gasless: true
4532
+ });
4533
+ if (paymentResponse.requires_signature && paymentResponse.unsigned_transaction) {
4534
+ const { Transaction: Transaction2 } = await this.getSolanaWeb3();
4535
+ const txBuffer = Uint8Array.from(atob(paymentResponse.unsigned_transaction), (c) => c.charCodeAt(0));
4536
+ const tx = Transaction2.from(txBuffer);
4537
+ tx.partialSign(keypair);
4538
+ const submitUrl = paymentResponse.submit_url || `/api/v1/ai/payments/${paymentResponse.payment_id}/submit-signed`;
4539
+ const submitResponse = await fetch(`${this.client["config"].baseURL}${submitUrl}`, {
4540
+ method: "POST",
4541
+ headers: {
4542
+ "Authorization": `Bearer ${this.client["config"].apiKey}`,
4543
+ "Content-Type": "application/json"
4544
+ },
4545
+ body: JSON.stringify({
4546
+ signed_transaction: btoa(String.fromCharCode(...tx.serialize()))
4547
+ })
4548
+ });
4549
+ if (!submitResponse.ok) {
4550
+ throw new Error(`Failed to submit signed transaction: ${submitResponse.statusText}`);
4551
+ }
4552
+ const submitData = await submitResponse.json();
4553
+ return {
4554
+ paymentId: paymentResponse.payment_id,
4555
+ status: submitData.status,
4556
+ signature: submitData.transaction_signature,
4557
+ confirmedInMs: submitData.confirmed_in_ms
4558
+ };
4559
+ }
4560
+ return {
4561
+ paymentId: paymentResponse.payment_id,
4562
+ status: paymentResponse.status,
4563
+ signature: paymentResponse.transaction_signature,
4564
+ confirmedInMs: paymentResponse.confirmed_in_ms
4565
+ };
4566
+ }
4567
+ /**
4568
+ * Check session key status
4569
+ */
4570
+ async getStatus() {
4571
+ if (!this.sessionKeyId) {
4572
+ throw new Error("No active session key");
4573
+ }
4574
+ return await this.client.sessionKeys.getStatus(this.sessionKeyId);
4575
+ }
4576
+ /**
4577
+ * Top up session key
4578
+ */
4579
+ async topUp(amount, userWallet, onApprovalNeeded) {
4580
+ if (!this.sessionKeyId || !this.deviceFingerprint) {
4581
+ throw new Error("No active session key");
4582
+ }
4583
+ const response = await this.client.sessionKeys.topUp(
4584
+ this.sessionKeyId,
4585
+ {
4586
+ user_wallet: userWallet,
4587
+ amount_usdc: amount,
4588
+ device_fingerprint: this.deviceFingerprint
4589
+ }
4590
+ );
4591
+ if (onApprovalNeeded) {
4592
+ await onApprovalNeeded(response.top_up_transaction);
4593
+ }
4594
+ }
4595
+ /**
4596
+ * Revoke session key
4597
+ */
4598
+ async revoke() {
4599
+ if (!this.sessionKeyId) {
4600
+ throw new Error("No active session key");
4601
+ }
4602
+ await this.client.sessionKeys.revoke(this.sessionKeyId);
4603
+ await this.cleanup();
4604
+ }
4605
+ /**
4606
+ * Cleanup (clear cache, reset state)
4607
+ */
4608
+ async cleanup() {
4609
+ if (this.config.cache && this.sessionKeyId) {
4610
+ await this.config.cache.invalidate(this.sessionKeyId);
4611
+ }
4612
+ this.sessionKeyId = null;
4613
+ this.sessionWallet = null;
4614
+ this.encryptedKey = null;
4615
+ this.deviceFingerprint = null;
4616
+ }
4617
+ /**
4618
+ * Get current session key ID
4619
+ */
4620
+ getSessionKeyId() {
4621
+ return this.sessionKeyId;
4622
+ }
4623
+ /**
4624
+ * Check if session is active
4625
+ */
4626
+ isActive() {
4627
+ return this.sessionKeyId !== null;
4628
+ }
4629
+ // ============================================
4630
+ // Private Helpers
4631
+ // ============================================
4632
+ async decryptKeypair(pin) {
4633
+ if (!this.encryptedKey || !this.deviceFingerprint) {
4634
+ throw new Error("No encrypted key available");
4635
+ }
4636
+ const { SessionKeyCrypto: SessionKeyCrypto2 } = await import("./device-bound-crypto-VX7SFVHT.mjs");
4637
+ const encrypted = {
4638
+ encryptedData: this.encryptedKey.ciphertext,
4639
+ nonce: this.encryptedKey.nonce,
4640
+ publicKey: "",
4641
+ // Not needed for decryption
4642
+ deviceFingerprint: this.deviceFingerprint,
4643
+ version: "argon2id-aes256gcm-v1"
4644
+ };
4645
+ return await SessionKeyCrypto2.decrypt(encrypted, pin, this.deviceFingerprint);
4646
+ }
4647
+ async generateDeviceFingerprint() {
4648
+ const { DeviceFingerprintGenerator: DeviceFingerprintGenerator2 } = await import("./device-bound-crypto-VX7SFVHT.mjs");
4649
+ const fingerprint = await DeviceFingerprintGenerator2.generate();
4650
+ return fingerprint.fingerprint;
4651
+ }
4652
+ async promptForPIN(message) {
4653
+ if (typeof window !== "undefined" && window.prompt) {
4654
+ const pin = window.prompt(message);
4655
+ if (!pin) {
4656
+ throw new Error("PIN required");
4657
+ }
4658
+ return pin;
4659
+ }
4660
+ throw new Error("PIN provider not configured and no browser prompt available");
4661
+ }
4662
+ async getSolanaWeb3() {
4663
+ try {
4664
+ return await import("@solana/web3.js");
4665
+ } catch {
4666
+ throw new Error("@solana/web3.js not installed. Install it to use device-bound payments.");
4667
+ }
4668
+ }
4669
+ };
4670
+ async function setupQuickSessionKey(client, config) {
4671
+ const { SessionKeyCache: SessionKeyCache2 } = await import("./cache-T5YPC7OK.mjs");
4672
+ const lifecycle = new SessionKeyLifecycle(client, {
4673
+ cache: new SessionKeyCache2({ storage: "localStorage", ttl: 36e5 }),
4674
+ autoCleanup: true
4675
+ });
4676
+ await lifecycle.createAndFund({
4677
+ userWallet: config.userWallet,
4678
+ agentId: config.agentId,
4679
+ limitUsdc: config.budgetUsdc,
4680
+ onApprovalNeeded: config.onApproval
4681
+ });
4682
+ return lifecycle;
4683
+ }
4684
+
4685
+ // src/helpers/dev.ts
4686
+ var DevTools = class {
4687
+ static debugEnabled = false;
4688
+ static requestLog = [];
4689
+ /**
4690
+ * Enable debug mode (logs all API requests/responses)
4691
+ */
4692
+ static enableDebugMode() {
4693
+ if (this.isDevelopment()) {
4694
+ this.debugEnabled = true;
4695
+ console.log("\u{1F527} ZendFi Debug Mode: ENABLED");
4696
+ console.log("All API requests will be logged to console");
4697
+ } else {
4698
+ console.warn("Debug mode can only be enabled in development environment");
4699
+ }
4700
+ }
4701
+ /**
4702
+ * Disable debug mode
4703
+ */
4704
+ static disableDebugMode() {
4705
+ this.debugEnabled = false;
4706
+ console.log("\u{1F527} ZendFi Debug Mode: DISABLED");
4707
+ }
4708
+ /**
4709
+ * Check if debug mode is enabled
4710
+ */
4711
+ static isDebugEnabled() {
4712
+ return this.debugEnabled;
4713
+ }
4714
+ /**
4715
+ * Log API request
4716
+ */
4717
+ static logRequest(method, url, body) {
4718
+ if (!this.debugEnabled) return;
4719
+ const timestamp = /* @__PURE__ */ new Date();
4720
+ console.group(`\u{1F4E4} API Request: ${method} ${url}`);
4721
+ console.log("Time:", timestamp.toISOString());
4722
+ if (body) {
4723
+ console.log("Body:", body);
4724
+ }
4725
+ console.groupEnd();
4726
+ this.requestLog.push({ timestamp, method, url });
4727
+ }
4728
+ /**
4729
+ * Log API response
4730
+ */
4731
+ static logResponse(method, url, status, data, duration) {
4732
+ if (!this.debugEnabled) return;
4733
+ const emoji = status >= 200 && status < 300 ? "\u2705" : "\u274C";
4734
+ console.group(`${emoji} API Response: ${method} ${url} [${status}]`);
4735
+ if (duration) {
4736
+ console.log("Duration:", `${duration}ms`);
4737
+ }
4738
+ console.log("Data:", data);
4739
+ console.groupEnd();
4740
+ const lastRequest = this.requestLog[this.requestLog.length - 1];
4741
+ if (lastRequest && lastRequest.method === method && lastRequest.url === url) {
4742
+ lastRequest.status = status;
4743
+ lastRequest.duration = duration;
4744
+ }
4745
+ }
4746
+ /**
4747
+ * Get request log
4748
+ */
4749
+ static getRequestLog() {
4750
+ return [...this.requestLog];
4751
+ }
4752
+ /**
4753
+ * Clear request log
4754
+ */
4755
+ static clearRequestLog() {
4756
+ this.requestLog = [];
4757
+ console.log("\u{1F5D1}\uFE0F Request log cleared");
4758
+ }
4759
+ /**
4760
+ * Create a test session key (devnet only)
4761
+ */
4762
+ static async createTestSessionKey() {
4763
+ if (!this.isDevelopment()) {
4764
+ throw new Error("Test session keys can only be created in development");
4765
+ }
4766
+ const { Keypair } = await this.getSolanaWeb3();
4767
+ const keypair = Keypair.generate();
4768
+ return {
4769
+ sessionKeyId: this.generateTestId("sk_test"),
4770
+ sessionWallet: keypair.publicKey.toString(),
4771
+ privateKey: keypair.secretKey,
4772
+ budget: 10
4773
+ // $10 test budget
4774
+ };
4775
+ }
4776
+ /**
4777
+ * Create a mock wallet for testing
4778
+ */
4779
+ static mockWallet(address) {
4780
+ const mockAddress = address || this.generateTestAddress();
4781
+ return {
4782
+ address: mockAddress,
4783
+ publicKey: { toString: () => mockAddress },
4784
+ signTransaction: async (tx) => {
4785
+ console.log("\u{1F527} Mock wallet: Signing transaction");
4786
+ return tx;
4787
+ },
4788
+ signMessage: async (_msg) => {
4789
+ console.log("\u{1F527} Mock wallet: Signing message");
4790
+ return {
4791
+ signature: new Uint8Array(64)
4792
+ // Mock signature
4793
+ };
4794
+ },
4795
+ isConnected: () => true,
4796
+ disconnect: async () => {
4797
+ console.log("\u{1F527} Mock wallet: Disconnected");
4798
+ }
4799
+ };
4800
+ }
4801
+ /**
4802
+ * Log transaction flow (visual diagram in console)
4803
+ */
4804
+ static logTransactionFlow(paymentId) {
4805
+ console.log(`
4806
+ \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
4807
+ \u2551 TRANSACTION FLOW \u2551
4808
+ \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
4809
+ \u2551 \u2551
4810
+ \u2551 Payment ID: ${paymentId} \u2551
4811
+ \u2551 \u2551
4812
+ \u2551 1. \u{1F3D7}\uFE0F Create Payment Intent \u2551
4813
+ \u2551 \u2514\u2500> POST /api/v1/ai/smart-payment \u2551
4814
+ \u2551 \u2551
4815
+ \u2551 2. \u{1F510} Sign Transaction (Device-Bound) \u2551
4816
+ \u2551 \u2514\u2500> Client-side signing with cached keypair \u2551
4817
+ \u2551 \u2551
4818
+ \u2551 3. \u{1F4E4} Submit Signed Transaction \u2551
4819
+ \u2551 \u2514\u2500> POST /api/v1/ai/payments/{id}/submit-signed \u2551
4820
+ \u2551 \u2551
4821
+ \u2551 4. \u23F3 Wait for Blockchain Confirmation \u2551
4822
+ \u2551 \u2514\u2500> Poll Solana RPC (~30-60 seconds) \u2551
4823
+ \u2551 \u2551
4824
+ \u2551 5. \u2705 Payment Confirmed \u2551
4825
+ \u2551 \u2514\u2500> Webhook fired: payment.confirmed \u2551
4826
+ \u2551 \u2551
4827
+ \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
4828
+ `);
4829
+ }
4830
+ /**
4831
+ * Log session key lifecycle
4832
+ */
4833
+ static logSessionKeyLifecycle(sessionKeyId) {
4834
+ console.log(`
4835
+ \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
4836
+ \u2551 SESSION KEY LIFECYCLE \u2551
4837
+ \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
4838
+ \u2551 \u2551
4839
+ \u2551 Session Key ID: ${sessionKeyId} \u2551
4840
+ \u2551 \u2551
4841
+ \u2551 Phase 1: CREATION \u2551
4842
+ \u2551 \u251C\u2500 Generate keypair (client-side) \u2551
4843
+ \u2551 \u251C\u2500 Encrypt with PIN + device fingerprint \u2551
4844
+ \u2551 \u251C\u2500 Send encrypted blob to backend \u2551
4845
+ \u2551 \u2514\u2500 Session key record created \u2551
4846
+ \u2551 \u2551
4847
+ \u2551 Phase 2: FUNDING \u2551
4848
+ \u2551 \u251C\u2500 Create top-up transaction \u2551
4849
+ \u2551 \u251C\u2500 User signs with main wallet \u2551
4850
+ \u2551 \u251C\u2500 Submit to Solana \u2551
4851
+ \u2551 \u2514\u2500 Session wallet funded \u2551
4852
+ \u2551 \u2551
4853
+ \u2551 Phase 3: USAGE \u2551
4854
+ \u2551 \u251C\u2500 Decrypt keypair with PIN \u2551
4855
+ \u2551 \u251C\u2500 Cache for 30min/1hr/24hr \u2551
4856
+ \u2551 \u251C\u2500 Sign payments automatically \u2551
4857
+ \u2551 \u2514\u2500 Track spending against limit \u2551
4858
+ \u2551 \u2551
4859
+ \u2551 Phase 4: REVOCATION \u2551
4860
+ \u2551 \u251C\u2500 Mark as inactive in DB \u2551
4861
+ \u2551 \u251C\u2500 Clear local cache \u2551
4862
+ \u2551 \u2514\u2500 Remaining funds locked \u2551
4863
+ \u2551 \u2551
4864
+ \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
4865
+ `);
4866
+ }
4867
+ /**
4868
+ * Benchmark API request
4869
+ */
4870
+ static async benchmarkRequest(name, fn) {
4871
+ const start = performance.now();
4872
+ const result = await fn();
4873
+ const duration = performance.now() - start;
4874
+ console.log(`\u23F1\uFE0F Benchmark [${name}]: ${duration.toFixed(2)}ms`);
4875
+ return {
4876
+ result,
4877
+ durationMs: duration
4878
+ };
4879
+ }
4880
+ /**
4881
+ * Stress test (send multiple concurrent requests)
4882
+ */
4883
+ static async stressTest(name, fn, concurrency = 10, iterations = 100) {
4884
+ console.log(`\u{1F525} Stress Test: ${name}`);
4885
+ console.log(`Concurrency: ${concurrency}, Iterations: ${iterations}`);
4886
+ const durations = [];
4887
+ let successful = 0;
4888
+ let failed = 0;
4889
+ for (let i = 0; i < iterations; i += concurrency) {
4890
+ const batch = Array(Math.min(concurrency, iterations - i)).fill(null).map(() => this.benchmarkRequest(`${name}-${i}`, fn));
4891
+ const results = await Promise.allSettled(batch);
4892
+ results.forEach((result) => {
4893
+ if (result.status === "fulfilled") {
4894
+ successful++;
4895
+ durations.push(result.value.durationMs);
4896
+ } else {
4897
+ failed++;
4898
+ }
4899
+ });
4900
+ }
4901
+ const stats = {
4902
+ totalRequests: iterations,
4903
+ successful,
4904
+ failed,
4905
+ avgDurationMs: durations.reduce((a, b) => a + b, 0) / durations.length,
4906
+ minDurationMs: Math.min(...durations),
4907
+ maxDurationMs: Math.max(...durations)
4908
+ };
4909
+ console.table(stats);
4910
+ return stats;
4911
+ }
4912
+ /**
4913
+ * Inspect ZendFi SDK configuration
4914
+ */
4915
+ static inspectConfig(client) {
4916
+ console.group("\u{1F50D} ZendFi SDK Configuration");
4917
+ console.log("Base URL:", client.config?.baseURL || "Unknown");
4918
+ console.log("API Key:", client.config?.apiKey ? `${client.config.apiKey.slice(0, 10)}...` : "Not set");
4919
+ console.log("Mode:", client.config?.mode || "Unknown");
4920
+ console.log("Environment:", client.config?.environment || "Unknown");
4921
+ console.log("Timeout:", client.config?.timeout || "Default");
4922
+ console.groupEnd();
4923
+ }
4924
+ /**
4925
+ * Generate test data
4926
+ */
4927
+ static generateTestData() {
4928
+ return {
4929
+ userWallet: this.generateTestAddress(),
4930
+ agentId: `test-agent-${Date.now()}`,
4931
+ sessionKeyId: this.generateTestId("sk_test"),
4932
+ paymentId: this.generateTestId("pay_test")
4933
+ };
4934
+ }
4935
+ /**
4936
+ * Check if running in development environment
4937
+ */
4938
+ static isDevelopment() {
4939
+ if (typeof process !== "undefined" && process.env) {
4940
+ return process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test";
4941
+ }
4942
+ if (typeof window !== "undefined" && window.location) {
4943
+ return window.location.hostname === "localhost" || window.location.hostname.includes("dev") || window.location.hostname.includes("staging");
4944
+ }
4945
+ return false;
4946
+ }
4947
+ /**
4948
+ * Generate test Solana address
4949
+ */
4950
+ static generateTestAddress() {
4951
+ const chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
4952
+ let address = "";
4953
+ for (let i = 0; i < 44; i++) {
4954
+ address += chars[Math.floor(Math.random() * chars.length)];
4955
+ }
4956
+ return address;
4957
+ }
4958
+ /**
4959
+ * Generate test ID with prefix
4960
+ */
4961
+ static generateTestId(prefix) {
4962
+ const id = Array(32).fill(null).map(() => Math.floor(Math.random() * 16).toString(16)).join("");
4963
+ return `${prefix}_${id}`;
4964
+ }
4965
+ /**
4966
+ * Get Solana Web3.js
4967
+ */
4968
+ static async getSolanaWeb3() {
4969
+ try {
4970
+ return await import("@solana/web3.js");
4971
+ } catch {
4972
+ throw new Error("@solana/web3.js not installed");
4973
+ }
4974
+ }
4975
+ };
4976
+ var PerformanceMonitor = class {
4977
+ metrics = /* @__PURE__ */ new Map();
4978
+ /**
4979
+ * Record a metric
4980
+ */
4981
+ record(name, value) {
4982
+ const values = this.metrics.get(name) || [];
4983
+ values.push(value);
4984
+ this.metrics.set(name, values);
4985
+ }
4986
+ /**
4987
+ * Get statistics for a metric
4988
+ */
4989
+ getStats(name) {
4990
+ const values = this.metrics.get(name);
4991
+ if (!values || values.length === 0) return null;
4992
+ const sorted = [...values].sort((a, b) => a - b);
4993
+ const count = values.length;
4994
+ return {
4995
+ count,
4996
+ avg: values.reduce((a, b) => a + b, 0) / count,
4997
+ min: sorted[0],
4998
+ max: sorted[count - 1],
4999
+ p50: sorted[Math.floor(count * 0.5)],
5000
+ p95: sorted[Math.floor(count * 0.95)],
5001
+ p99: sorted[Math.floor(count * 0.99)]
5002
+ };
5003
+ }
5004
+ /**
5005
+ * Get all metrics
5006
+ */
5007
+ getAllStats() {
5008
+ const stats = {};
5009
+ for (const [name] of this.metrics) {
5010
+ stats[name] = this.getStats(name);
5011
+ }
5012
+ return stats;
5013
+ }
5014
+ /**
5015
+ * Print report
5016
+ */
5017
+ printReport() {
5018
+ console.table(this.getAllStats());
5019
+ }
5020
+ /**
5021
+ * Clear all metrics
5022
+ */
5023
+ clear() {
5024
+ this.metrics.clear();
5025
+ }
5026
+ };
3451
5027
  export {
3452
5028
  AgentAPI,
5029
+ AnthropicAdapter,
3453
5030
  ApiError,
3454
5031
  AuthenticationError2 as AuthenticationError,
3455
5032
  AutonomyAPI,
3456
5033
  ConfigLoader,
5034
+ DevTools,
3457
5035
  DeviceBoundSessionKey,
3458
5036
  DeviceFingerprintGenerator,
3459
5037
  ERROR_CODES,
5038
+ ErrorRecovery,
5039
+ GeminiAdapter,
3460
5040
  InterceptorManager,
3461
5041
  LitCryptoSigner,
3462
5042
  NetworkError2 as NetworkError,
5043
+ OpenAIAdapter,
5044
+ PINRateLimiter,
5045
+ PINValidator,
3463
5046
  PaymentError,
5047
+ PaymentIntentParser,
3464
5048
  PaymentIntentsAPI,
5049
+ PerformanceMonitor,
3465
5050
  PricingAPI,
5051
+ QuickCaches,
3466
5052
  RateLimitError2 as RateLimitError,
3467
5053
  RateLimiter,
3468
5054
  RecoveryQRGenerator,
5055
+ RetryStrategy,
3469
5056
  SPENDING_LIMIT_ACTION_CID,
5057
+ SecureStorage,
5058
+ SessionKeyCache,
3470
5059
  SessionKeyCrypto,
5060
+ SessionKeyLifecycle,
3471
5061
  SessionKeysAPI,
3472
5062
  SmartPaymentsAPI,
5063
+ TransactionMonitor,
5064
+ TransactionPoller,
3473
5065
  ValidationError2 as ValidationError,
5066
+ WalletConnector,
3474
5067
  WebhookError,
3475
5068
  ZendFiClient,
3476
5069
  ZendFiError2 as ZendFiError,
@@ -3485,6 +5078,7 @@ export {
3485
5078
  asPaymentLinkCode,
3486
5079
  asSessionId,
3487
5080
  asSubscriptionId,
5081
+ createWalletHook,
3488
5082
  createZendFiError,
3489
5083
  decodeSignatureFromLit,
3490
5084
  encodeTransactionForLit,
@@ -3492,6 +5086,7 @@ export {
3492
5086
  isZendFiError,
3493
5087
  processWebhook,
3494
5088
  requiresLitSigning,
5089
+ setupQuickSessionKey,
3495
5090
  sleep,
3496
5091
  verifyExpressWebhook,
3497
5092
  verifyNextWebhook,