@zendfi/sdk 0.8.4 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,907 @@
1
+ import {
2
+ __require
3
+ } from "./chunk-Y6FXYEAI.mjs";
4
+
5
+ // src/helpers/wallet.ts
6
+ var WalletConnector = class _WalletConnector {
7
+ static connectedWallet = null;
8
+ /**
9
+ * Detect and connect to a Solana wallet
10
+ */
11
+ static async detectAndConnect(config = {}) {
12
+ if (this.connectedWallet && this.connectedWallet.isConnected()) {
13
+ return this.connectedWallet;
14
+ }
15
+ const detected = this.detectWallets();
16
+ if (detected.length === 0) {
17
+ if (config.showInstallPrompt !== false) {
18
+ this.showInstallPrompt();
19
+ }
20
+ throw new Error("No Solana wallet detected. Please install Phantom, Solflare, or Backpack.");
21
+ }
22
+ let selectedProvider = detected[0];
23
+ if (config.preferredProvider && detected.includes(config.preferredProvider)) {
24
+ selectedProvider = config.preferredProvider;
25
+ }
26
+ const wallet = await this.connectToProvider(selectedProvider);
27
+ this.connectedWallet = wallet;
28
+ return wallet;
29
+ }
30
+ /**
31
+ * Detect available Solana wallets
32
+ */
33
+ static detectWallets() {
34
+ const detected = [];
35
+ if (typeof window === "undefined") return detected;
36
+ if (window.solana?.isPhantom) detected.push("phantom");
37
+ if (window.solflare?.isSolflare) detected.push("solflare");
38
+ if (window.backpack?.isBackpack) detected.push("backpack");
39
+ if (window.coinbaseSolana) detected.push("coinbase");
40
+ if (window.trustwallet?.solana) detected.push("trust");
41
+ return detected;
42
+ }
43
+ /**
44
+ * Connect to a specific wallet provider
45
+ */
46
+ static async connectToProvider(provider) {
47
+ if (typeof window === "undefined") {
48
+ throw new Error("Wallet connection only works in browser environment");
49
+ }
50
+ let adapter;
51
+ switch (provider) {
52
+ case "phantom":
53
+ adapter = window.solana;
54
+ if (!adapter?.isPhantom) {
55
+ throw new Error("Phantom wallet not found");
56
+ }
57
+ break;
58
+ case "solflare":
59
+ adapter = window.solflare;
60
+ if (!adapter?.isSolflare) {
61
+ throw new Error("Solflare wallet not found");
62
+ }
63
+ break;
64
+ case "backpack":
65
+ adapter = window.backpack;
66
+ if (!adapter?.isBackpack) {
67
+ throw new Error("Backpack wallet not found");
68
+ }
69
+ break;
70
+ case "coinbase":
71
+ adapter = window.coinbaseSolana;
72
+ if (!adapter) {
73
+ throw new Error("Coinbase Wallet not found");
74
+ }
75
+ break;
76
+ case "trust":
77
+ adapter = window.trustwallet?.solana;
78
+ if (!adapter) {
79
+ throw new Error("Trust Wallet not found");
80
+ }
81
+ break;
82
+ default:
83
+ throw new Error(`Unknown wallet provider: ${provider}`);
84
+ }
85
+ try {
86
+ const response = await adapter.connect();
87
+ const publicKey = response.publicKey || adapter.publicKey;
88
+ if (!publicKey) {
89
+ throw new Error("Failed to get wallet public key");
90
+ }
91
+ const connectedWallet = {
92
+ address: publicKey.toString(),
93
+ provider,
94
+ publicKey,
95
+ signTransaction: async (tx) => {
96
+ return await adapter.signTransaction(tx);
97
+ },
98
+ signAllTransactions: async (txs) => {
99
+ if (adapter.signAllTransactions) {
100
+ return await adapter.signAllTransactions(txs);
101
+ }
102
+ const signed = [];
103
+ for (const tx of txs) {
104
+ signed.push(await adapter.signTransaction(tx));
105
+ }
106
+ return signed;
107
+ },
108
+ signMessage: async (message) => {
109
+ if (adapter.signMessage) {
110
+ return await adapter.signMessage(message);
111
+ }
112
+ throw new Error(`${provider} does not support message signing`);
113
+ },
114
+ disconnect: async () => {
115
+ if (adapter.disconnect) {
116
+ await adapter.disconnect();
117
+ }
118
+ _WalletConnector.connectedWallet = null;
119
+ },
120
+ isConnected: () => {
121
+ return adapter.isConnected ?? false;
122
+ },
123
+ raw: adapter
124
+ };
125
+ return connectedWallet;
126
+ } catch (error) {
127
+ throw new Error(`Failed to connect to ${provider}: ${error.message}`);
128
+ }
129
+ }
130
+ /**
131
+ * Sign and submit a transaction
132
+ */
133
+ static async signAndSubmit(transaction, wallet, connection) {
134
+ const signedTx = await wallet.signTransaction(transaction);
135
+ const signature = await connection.sendRawTransaction(signedTx.serialize(), {
136
+ skipPreflight: false,
137
+ preflightCommitment: "confirmed"
138
+ });
139
+ return { signature };
140
+ }
141
+ /**
142
+ * Get current connected wallet
143
+ */
144
+ static getConnectedWallet() {
145
+ return this.connectedWallet;
146
+ }
147
+ /**
148
+ * Disconnect current wallet
149
+ */
150
+ static async disconnect() {
151
+ if (this.connectedWallet) {
152
+ await this.connectedWallet.disconnect();
153
+ this.connectedWallet = null;
154
+ }
155
+ }
156
+ /**
157
+ * Listen for wallet connection changes
158
+ */
159
+ static onAccountChange(callback) {
160
+ if (typeof window === "undefined") {
161
+ return () => {
162
+ };
163
+ }
164
+ const cleanupFns = [];
165
+ if (window.solana?.on) {
166
+ window.solana.on("accountChanged", callback);
167
+ cleanupFns.push(() => {
168
+ window.solana?.removeListener("accountChanged", callback);
169
+ });
170
+ }
171
+ if (window.solflare?.on) {
172
+ window.solflare.on("accountChanged", callback);
173
+ cleanupFns.push(() => {
174
+ window.solflare?.removeListener("accountChanged", callback);
175
+ });
176
+ }
177
+ return () => {
178
+ cleanupFns.forEach((fn) => fn());
179
+ };
180
+ }
181
+ /**
182
+ * Listen for wallet disconnection
183
+ */
184
+ static onDisconnect(callback) {
185
+ if (typeof window === "undefined") {
186
+ return () => {
187
+ };
188
+ }
189
+ const cleanupFns = [];
190
+ if (window.solana?.on) {
191
+ window.solana.on("disconnect", callback);
192
+ cleanupFns.push(() => {
193
+ window.solana?.removeListener("disconnect", callback);
194
+ });
195
+ }
196
+ if (window.solflare?.on) {
197
+ window.solflare.on("disconnect", callback);
198
+ cleanupFns.push(() => {
199
+ window.solflare?.removeListener("disconnect", callback);
200
+ });
201
+ }
202
+ return () => {
203
+ cleanupFns.forEach((fn) => fn());
204
+ };
205
+ }
206
+ /**
207
+ * Show install prompt UI
208
+ */
209
+ static showInstallPrompt() {
210
+ const message = `
211
+ No Solana wallet detected!
212
+
213
+ Install one of these wallets:
214
+ \u2022 Phantom: https://phantom.app
215
+ \u2022 Solflare: https://solflare.com
216
+ \u2022 Backpack: https://backpack.app
217
+ `.trim();
218
+ console.warn(message);
219
+ if (typeof window !== "undefined") {
220
+ const userChoice = window.confirm(
221
+ "No Solana wallet detected.\n\nWould you like to install Phantom wallet?"
222
+ );
223
+ if (userChoice) {
224
+ window.open("https://phantom.app", "_blank");
225
+ }
226
+ }
227
+ }
228
+ /**
229
+ * Check if wallet is installed
230
+ */
231
+ static isWalletInstalled(provider) {
232
+ return this.detectWallets().includes(provider);
233
+ }
234
+ /**
235
+ * Get wallet download URL
236
+ */
237
+ static getWalletUrl(provider) {
238
+ const urls = {
239
+ phantom: "https://phantom.app",
240
+ solflare: "https://solflare.com",
241
+ backpack: "https://backpack.app",
242
+ coinbase: "https://www.coinbase.com/wallet",
243
+ trust: "https://trustwallet.com"
244
+ };
245
+ return urls[provider];
246
+ }
247
+ };
248
+ function createWalletHook() {
249
+ let useState;
250
+ let useEffect;
251
+ try {
252
+ const React = __require("react");
253
+ useState = React.useState;
254
+ useEffect = React.useEffect;
255
+ } catch {
256
+ throw new Error("React not found. Install react to use wallet hooks.");
257
+ }
258
+ return function useWallet() {
259
+ const [wallet, setWallet] = useState(null);
260
+ const [connecting, setConnecting] = useState(false);
261
+ const [error, setError] = useState(null);
262
+ const connect = async (config) => {
263
+ setConnecting(true);
264
+ setError(null);
265
+ try {
266
+ const connected = await WalletConnector.detectAndConnect(config);
267
+ setWallet(connected);
268
+ } catch (err) {
269
+ setError(err);
270
+ } finally {
271
+ setConnecting(false);
272
+ }
273
+ };
274
+ const disconnect = async () => {
275
+ await WalletConnector.disconnect();
276
+ setWallet(null);
277
+ };
278
+ useEffect(() => {
279
+ const cleanup = WalletConnector.onAccountChange((publicKey) => {
280
+ if (wallet) {
281
+ setWallet({ ...wallet, publicKey, address: publicKey.toString() });
282
+ }
283
+ });
284
+ return cleanup;
285
+ }, [wallet]);
286
+ return {
287
+ wallet,
288
+ connecting,
289
+ error,
290
+ connect,
291
+ disconnect,
292
+ isConnected: wallet !== null
293
+ };
294
+ };
295
+ }
296
+
297
+ // src/helpers/polling.ts
298
+ var TransactionPoller = class {
299
+ /**
300
+ * Wait for transaction confirmation
301
+ */
302
+ static async waitForConfirmation(signature, options = {}) {
303
+ const {
304
+ timeout = 6e4,
305
+ interval = 2e3,
306
+ maxInterval = 1e4,
307
+ maxAttempts = 30,
308
+ commitment = "confirmed",
309
+ rpcUrl
310
+ } = options;
311
+ const startTime = Date.now();
312
+ let currentInterval = interval;
313
+ let attempts = 0;
314
+ while (true) {
315
+ attempts++;
316
+ if (Date.now() - startTime > timeout) {
317
+ return {
318
+ confirmed: false,
319
+ signature,
320
+ error: `Transaction confirmation timeout after ${timeout}ms`
321
+ };
322
+ }
323
+ if (attempts > maxAttempts) {
324
+ return {
325
+ confirmed: false,
326
+ signature,
327
+ error: `Maximum polling attempts (${maxAttempts}) exceeded`
328
+ };
329
+ }
330
+ try {
331
+ const status = await this.checkTransactionStatus(signature, commitment, rpcUrl);
332
+ if (status.confirmed) {
333
+ return status;
334
+ }
335
+ if (status.error) {
336
+ return status;
337
+ }
338
+ await this.sleep(currentInterval);
339
+ currentInterval = Math.min(currentInterval * 1.5, maxInterval);
340
+ } catch (error) {
341
+ await this.sleep(currentInterval);
342
+ currentInterval = Math.min(currentInterval * 1.5, maxInterval);
343
+ }
344
+ }
345
+ }
346
+ /**
347
+ * Check transaction status via RPC
348
+ */
349
+ static async checkTransactionStatus(signature, commitment = "confirmed", rpcUrl) {
350
+ const endpoint = rpcUrl || this.getDefaultRpcUrl();
351
+ const response = await fetch(endpoint, {
352
+ method: "POST",
353
+ headers: { "Content-Type": "application/json" },
354
+ body: JSON.stringify({
355
+ jsonrpc: "2.0",
356
+ id: 1,
357
+ method: "getSignatureStatuses",
358
+ params: [[signature], { searchTransactionHistory: true }]
359
+ })
360
+ });
361
+ if (!response.ok) {
362
+ throw new Error(`RPC error: ${response.status} ${response.statusText}`);
363
+ }
364
+ const data = await response.json();
365
+ if (data.error) {
366
+ return {
367
+ confirmed: false,
368
+ signature,
369
+ error: data.error.message || "RPC error"
370
+ };
371
+ }
372
+ const status = data.result?.value?.[0];
373
+ if (!status) {
374
+ return {
375
+ confirmed: false,
376
+ signature
377
+ };
378
+ }
379
+ const isConfirmed = this.isCommitmentReached(status, commitment);
380
+ return {
381
+ confirmed: isConfirmed,
382
+ signature,
383
+ slot: status.slot,
384
+ confirmations: status.confirmations,
385
+ error: status.err ? JSON.stringify(status.err) : void 0
386
+ };
387
+ }
388
+ /**
389
+ * Check if commitment level is reached
390
+ */
391
+ static isCommitmentReached(status, commitment) {
392
+ if (status.err) return false;
393
+ switch (commitment) {
394
+ case "processed":
395
+ return true;
396
+ // Any status means processed
397
+ case "confirmed":
398
+ return status.confirmationStatus === "confirmed" || status.confirmationStatus === "finalized";
399
+ case "finalized":
400
+ return status.confirmationStatus === "finalized";
401
+ default:
402
+ return false;
403
+ }
404
+ }
405
+ /**
406
+ * Get default RPC URL based on environment
407
+ */
408
+ static getDefaultRpcUrl() {
409
+ if (typeof window !== "undefined" && window.location) {
410
+ const hostname = window.location.hostname;
411
+ if (hostname.includes("localhost") || hostname.includes("dev")) {
412
+ return "https://api.devnet.solana.com";
413
+ }
414
+ }
415
+ return "https://api.mainnet-beta.solana.com";
416
+ }
417
+ /**
418
+ * Poll multiple transactions in parallel
419
+ */
420
+ static async waitForMultiple(signatures, options = {}) {
421
+ return await Promise.all(
422
+ signatures.map((sig) => this.waitForConfirmation(sig, options))
423
+ );
424
+ }
425
+ /**
426
+ * Get transaction details after confirmation
427
+ */
428
+ static async getTransactionDetails(signature, rpcUrl) {
429
+ const endpoint = rpcUrl || this.getDefaultRpcUrl();
430
+ const response = await fetch(endpoint, {
431
+ method: "POST",
432
+ headers: { "Content-Type": "application/json" },
433
+ body: JSON.stringify({
434
+ jsonrpc: "2.0",
435
+ id: 1,
436
+ method: "getTransaction",
437
+ params: [
438
+ signature,
439
+ {
440
+ encoding: "jsonParsed",
441
+ commitment: "confirmed",
442
+ maxSupportedTransactionVersion: 0
443
+ }
444
+ ]
445
+ })
446
+ });
447
+ if (!response.ok) {
448
+ throw new Error(`RPC error: ${response.status}`);
449
+ }
450
+ const data = await response.json();
451
+ if (data.error) {
452
+ throw new Error(data.error.message || "Failed to get transaction");
453
+ }
454
+ return data.result;
455
+ }
456
+ /**
457
+ * Check if transaction exists on chain
458
+ */
459
+ static async exists(signature, rpcUrl) {
460
+ try {
461
+ const status = await this.checkTransactionStatus(signature, "confirmed", rpcUrl);
462
+ return status.confirmed || !!status.slot;
463
+ } catch {
464
+ return false;
465
+ }
466
+ }
467
+ /**
468
+ * Get recent blockhash (useful for transaction building)
469
+ */
470
+ static async getRecentBlockhash(rpcUrl) {
471
+ const endpoint = rpcUrl || this.getDefaultRpcUrl();
472
+ const response = await fetch(endpoint, {
473
+ method: "POST",
474
+ headers: { "Content-Type": "application/json" },
475
+ body: JSON.stringify({
476
+ jsonrpc: "2.0",
477
+ id: 1,
478
+ method: "getLatestBlockhash",
479
+ params: [{ commitment: "finalized" }]
480
+ })
481
+ });
482
+ if (!response.ok) {
483
+ throw new Error(`RPC error: ${response.status}`);
484
+ }
485
+ const data = await response.json();
486
+ if (data.error) {
487
+ throw new Error(data.error.message);
488
+ }
489
+ return data.result.value;
490
+ }
491
+ /**
492
+ * Sleep utility
493
+ */
494
+ static sleep(ms) {
495
+ return new Promise((resolve) => setTimeout(resolve, ms));
496
+ }
497
+ };
498
+ var TransactionMonitor = class {
499
+ monitors = /* @__PURE__ */ new Map();
500
+ /**
501
+ * Start monitoring a transaction
502
+ */
503
+ monitor(signature, callbacks, options = {}) {
504
+ this.stopMonitoring(signature);
505
+ const { timeout = 6e4, interval = 2e3 } = options;
506
+ const startTime = Date.now();
507
+ const intervalId = setInterval(async () => {
508
+ if (Date.now() - startTime > timeout) {
509
+ this.stopMonitoring(signature);
510
+ callbacks.onTimeout?.();
511
+ return;
512
+ }
513
+ try {
514
+ const status = await TransactionPoller.waitForConfirmation(signature, {
515
+ ...options,
516
+ maxAttempts: 1
517
+ // Single check per interval
518
+ });
519
+ if (status.confirmed) {
520
+ this.stopMonitoring(signature);
521
+ callbacks.onConfirmed?.(status);
522
+ } else if (status.error) {
523
+ this.stopMonitoring(signature);
524
+ callbacks.onFailed?.(status);
525
+ }
526
+ } catch (error) {
527
+ }
528
+ }, interval);
529
+ this.monitors.set(signature, { interval: intervalId, callbacks });
530
+ }
531
+ /**
532
+ * Stop monitoring a transaction
533
+ */
534
+ stopMonitoring(signature) {
535
+ const monitor = this.monitors.get(signature);
536
+ if (monitor) {
537
+ clearInterval(monitor.interval);
538
+ this.monitors.delete(signature);
539
+ }
540
+ }
541
+ /**
542
+ * Stop all monitors
543
+ */
544
+ stopAll() {
545
+ for (const [signature] of this.monitors) {
546
+ this.stopMonitoring(signature);
547
+ }
548
+ }
549
+ /**
550
+ * Get active monitors
551
+ */
552
+ getActiveMonitors() {
553
+ return Array.from(this.monitors.keys());
554
+ }
555
+ };
556
+
557
+ // src/helpers/dev.ts
558
+ var DevTools = class {
559
+ static debugEnabled = false;
560
+ static requestLog = [];
561
+ /**
562
+ * Enable debug mode (logs all API requests/responses)
563
+ */
564
+ static enableDebugMode() {
565
+ if (this.isDevelopment()) {
566
+ this.debugEnabled = true;
567
+ console.log("\u{1F527} ZendFi Debug Mode: ENABLED");
568
+ console.log("All API requests will be logged to console");
569
+ } else {
570
+ console.warn("Debug mode can only be enabled in development environment");
571
+ }
572
+ }
573
+ /**
574
+ * Disable debug mode
575
+ */
576
+ static disableDebugMode() {
577
+ this.debugEnabled = false;
578
+ console.log("\u{1F527} ZendFi Debug Mode: DISABLED");
579
+ }
580
+ /**
581
+ * Check if debug mode is enabled
582
+ */
583
+ static isDebugEnabled() {
584
+ return this.debugEnabled;
585
+ }
586
+ /**
587
+ * Log API request
588
+ */
589
+ static logRequest(method, url, body) {
590
+ if (!this.debugEnabled) return;
591
+ const timestamp = /* @__PURE__ */ new Date();
592
+ console.group(`\u{1F4E4} API Request: ${method} ${url}`);
593
+ console.log("Time:", timestamp.toISOString());
594
+ if (body) {
595
+ console.log("Body:", body);
596
+ }
597
+ console.groupEnd();
598
+ this.requestLog.push({ timestamp, method, url });
599
+ }
600
+ /**
601
+ * Log API response
602
+ */
603
+ static logResponse(method, url, status, data, duration) {
604
+ if (!this.debugEnabled) return;
605
+ const emoji = status >= 200 && status < 300 ? "\u2705" : "\u274C";
606
+ console.group(`${emoji} API Response: ${method} ${url} [${status}]`);
607
+ if (duration) {
608
+ console.log("Duration:", `${duration}ms`);
609
+ }
610
+ console.log("Data:", data);
611
+ console.groupEnd();
612
+ const lastRequest = this.requestLog[this.requestLog.length - 1];
613
+ if (lastRequest && lastRequest.method === method && lastRequest.url === url) {
614
+ lastRequest.status = status;
615
+ lastRequest.duration = duration;
616
+ }
617
+ }
618
+ /**
619
+ * Get request log
620
+ */
621
+ static getRequestLog() {
622
+ return [...this.requestLog];
623
+ }
624
+ /**
625
+ * Clear request log
626
+ */
627
+ static clearRequestLog() {
628
+ this.requestLog = [];
629
+ console.log("\u{1F5D1}\uFE0F Request log cleared");
630
+ }
631
+ /**
632
+ * Create a test session key (devnet only)
633
+ */
634
+ static async createTestSessionKey() {
635
+ if (!this.isDevelopment()) {
636
+ throw new Error("Test session keys can only be created in development");
637
+ }
638
+ const { Keypair } = await this.getSolanaWeb3();
639
+ const keypair = Keypair.generate();
640
+ return {
641
+ sessionKeyId: this.generateTestId("sk_test"),
642
+ sessionWallet: keypair.publicKey.toString(),
643
+ privateKey: keypair.secretKey,
644
+ budget: 10
645
+ // $10 test budget
646
+ };
647
+ }
648
+ /**
649
+ * Create a mock wallet for testing
650
+ */
651
+ static mockWallet(address) {
652
+ const mockAddress = address || this.generateTestAddress();
653
+ return {
654
+ address: mockAddress,
655
+ publicKey: { toString: () => mockAddress },
656
+ signTransaction: async (tx) => {
657
+ console.log("\u{1F527} Mock wallet: Signing transaction");
658
+ return tx;
659
+ },
660
+ signMessage: async (_msg) => {
661
+ console.log("\u{1F527} Mock wallet: Signing message");
662
+ return {
663
+ signature: new Uint8Array(64)
664
+ // Mock signature
665
+ };
666
+ },
667
+ isConnected: () => true,
668
+ disconnect: async () => {
669
+ console.log("\u{1F527} Mock wallet: Disconnected");
670
+ }
671
+ };
672
+ }
673
+ /**
674
+ * Log transaction flow (visual diagram in console)
675
+ */
676
+ static logTransactionFlow(paymentId) {
677
+ console.log(`
678
+ \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
679
+ \u2551 TRANSACTION FLOW \u2551
680
+ \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
681
+ \u2551 \u2551
682
+ \u2551 Payment ID: ${paymentId} \u2551
683
+ \u2551 \u2551
684
+ \u2551 1. \u{1F3D7}\uFE0F Create Payment Intent \u2551
685
+ \u2551 \u2514\u2500> POST /api/v1/ai/smart-payment \u2551
686
+ \u2551 \u2551
687
+ \u2551 2. \u{1F510} Sign Transaction (Device-Bound) \u2551
688
+ \u2551 \u2514\u2500> Client-side signing with cached keypair \u2551
689
+ \u2551 \u2551
690
+ \u2551 3. \u{1F4E4} Submit Signed Transaction \u2551
691
+ \u2551 \u2514\u2500> POST /api/v1/ai/payments/{id}/submit-signed \u2551
692
+ \u2551 \u2551
693
+ \u2551 4. \u23F3 Wait for Blockchain Confirmation \u2551
694
+ \u2551 \u2514\u2500> Poll Solana RPC (~30-60 seconds) \u2551
695
+ \u2551 \u2551
696
+ \u2551 5. \u2705 Payment Confirmed \u2551
697
+ \u2551 \u2514\u2500> Webhook fired: payment.confirmed \u2551
698
+ \u2551 \u2551
699
+ \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
700
+ `);
701
+ }
702
+ /**
703
+ * Log session key lifecycle
704
+ */
705
+ static logSessionKeyLifecycle(sessionKeyId) {
706
+ console.log(`
707
+ \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
708
+ \u2551 SESSION KEY LIFECYCLE \u2551
709
+ \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
710
+ \u2551 \u2551
711
+ \u2551 Session Key ID: ${sessionKeyId} \u2551
712
+ \u2551 \u2551
713
+ \u2551 Phase 1: CREATION \u2551
714
+ \u2551 \u251C\u2500 Generate keypair (client-side) \u2551
715
+ \u2551 \u251C\u2500 Encrypt with PIN + device fingerprint \u2551
716
+ \u2551 \u251C\u2500 Send encrypted blob to backend \u2551
717
+ \u2551 \u2514\u2500 Session key record created \u2551
718
+ \u2551 \u2551
719
+ \u2551 Phase 2: FUNDING \u2551
720
+ \u2551 \u251C\u2500 Create top-up transaction \u2551
721
+ \u2551 \u251C\u2500 User signs with main wallet \u2551
722
+ \u2551 \u251C\u2500 Submit to Solana \u2551
723
+ \u2551 \u2514\u2500 Session wallet funded \u2551
724
+ \u2551 \u2551
725
+ \u2551 Phase 3: USAGE \u2551
726
+ \u2551 \u251C\u2500 Decrypt keypair with PIN \u2551
727
+ \u2551 \u251C\u2500 Cache for 30min/1hr/24hr \u2551
728
+ \u2551 \u251C\u2500 Sign payments automatically \u2551
729
+ \u2551 \u2514\u2500 Track spending against limit \u2551
730
+ \u2551 \u2551
731
+ \u2551 Phase 4: REVOCATION \u2551
732
+ \u2551 \u251C\u2500 Mark as inactive in DB \u2551
733
+ \u2551 \u251C\u2500 Clear local cache \u2551
734
+ \u2551 \u2514\u2500 Remaining funds locked \u2551
735
+ \u2551 \u2551
736
+ \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
737
+ `);
738
+ }
739
+ /**
740
+ * Benchmark API request
741
+ */
742
+ static async benchmarkRequest(name, fn) {
743
+ const start = performance.now();
744
+ const result = await fn();
745
+ const duration = performance.now() - start;
746
+ console.log(`\u23F1\uFE0F Benchmark [${name}]: ${duration.toFixed(2)}ms`);
747
+ return {
748
+ result,
749
+ durationMs: duration
750
+ };
751
+ }
752
+ /**
753
+ * Stress test (send multiple concurrent requests)
754
+ */
755
+ static async stressTest(name, fn, concurrency = 10, iterations = 100) {
756
+ console.log(`\u{1F525} Stress Test: ${name}`);
757
+ console.log(`Concurrency: ${concurrency}, Iterations: ${iterations}`);
758
+ const durations = [];
759
+ let successful = 0;
760
+ let failed = 0;
761
+ for (let i = 0; i < iterations; i += concurrency) {
762
+ const batch = Array(Math.min(concurrency, iterations - i)).fill(null).map(() => this.benchmarkRequest(`${name}-${i}`, fn));
763
+ const results = await Promise.allSettled(batch);
764
+ results.forEach((result) => {
765
+ if (result.status === "fulfilled") {
766
+ successful++;
767
+ durations.push(result.value.durationMs);
768
+ } else {
769
+ failed++;
770
+ }
771
+ });
772
+ }
773
+ const stats = {
774
+ totalRequests: iterations,
775
+ successful,
776
+ failed,
777
+ avgDurationMs: durations.reduce((a, b) => a + b, 0) / durations.length,
778
+ minDurationMs: Math.min(...durations),
779
+ maxDurationMs: Math.max(...durations)
780
+ };
781
+ console.table(stats);
782
+ return stats;
783
+ }
784
+ /**
785
+ * Inspect ZendFi SDK configuration
786
+ */
787
+ static inspectConfig(client) {
788
+ console.group("\u{1F50D} ZendFi SDK Configuration");
789
+ console.log("Base URL:", client.config?.baseURL || "Unknown");
790
+ console.log("API Key:", client.config?.apiKey ? `${client.config.apiKey.slice(0, 10)}...` : "Not set");
791
+ console.log("Mode:", client.config?.mode || "Unknown");
792
+ console.log("Environment:", client.config?.environment || "Unknown");
793
+ console.log("Timeout:", client.config?.timeout || "Default");
794
+ console.groupEnd();
795
+ }
796
+ /**
797
+ * Generate test data
798
+ */
799
+ static generateTestData() {
800
+ return {
801
+ userWallet: this.generateTestAddress(),
802
+ agentId: `test-agent-${Date.now()}`,
803
+ sessionKeyId: this.generateTestId("sk_test"),
804
+ paymentId: this.generateTestId("pay_test")
805
+ };
806
+ }
807
+ /**
808
+ * Check if running in development environment
809
+ */
810
+ static isDevelopment() {
811
+ if (typeof process !== "undefined" && process.env) {
812
+ return process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test";
813
+ }
814
+ if (typeof window !== "undefined" && window.location) {
815
+ return window.location.hostname === "localhost" || window.location.hostname.includes("dev") || window.location.hostname.includes("staging");
816
+ }
817
+ return false;
818
+ }
819
+ /**
820
+ * Generate test Solana address
821
+ */
822
+ static generateTestAddress() {
823
+ const chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
824
+ let address = "";
825
+ for (let i = 0; i < 44; i++) {
826
+ address += chars[Math.floor(Math.random() * chars.length)];
827
+ }
828
+ return address;
829
+ }
830
+ /**
831
+ * Generate test ID with prefix
832
+ */
833
+ static generateTestId(prefix) {
834
+ const id = Array(32).fill(null).map(() => Math.floor(Math.random() * 16).toString(16)).join("");
835
+ return `${prefix}_${id}`;
836
+ }
837
+ /**
838
+ * Get Solana Web3.js
839
+ */
840
+ static async getSolanaWeb3() {
841
+ try {
842
+ return await import("@solana/web3.js");
843
+ } catch {
844
+ throw new Error("@solana/web3.js not installed");
845
+ }
846
+ }
847
+ };
848
+ var PerformanceMonitor = class {
849
+ metrics = /* @__PURE__ */ new Map();
850
+ /**
851
+ * Record a metric
852
+ */
853
+ record(name, value) {
854
+ const values = this.metrics.get(name) || [];
855
+ values.push(value);
856
+ this.metrics.set(name, values);
857
+ }
858
+ /**
859
+ * Get statistics for a metric
860
+ */
861
+ getStats(name) {
862
+ const values = this.metrics.get(name);
863
+ if (!values || values.length === 0) return null;
864
+ const sorted = [...values].sort((a, b) => a - b);
865
+ const count = values.length;
866
+ return {
867
+ count,
868
+ avg: values.reduce((a, b) => a + b, 0) / count,
869
+ min: sorted[0],
870
+ max: sorted[count - 1],
871
+ p50: sorted[Math.floor(count * 0.5)],
872
+ p95: sorted[Math.floor(count * 0.95)],
873
+ p99: sorted[Math.floor(count * 0.99)]
874
+ };
875
+ }
876
+ /**
877
+ * Get all metrics
878
+ */
879
+ getAllStats() {
880
+ const stats = {};
881
+ for (const [name] of this.metrics) {
882
+ stats[name] = this.getStats(name);
883
+ }
884
+ return stats;
885
+ }
886
+ /**
887
+ * Print report
888
+ */
889
+ printReport() {
890
+ console.table(this.getAllStats());
891
+ }
892
+ /**
893
+ * Clear all metrics
894
+ */
895
+ clear() {
896
+ this.metrics.clear();
897
+ }
898
+ };
899
+
900
+ export {
901
+ WalletConnector,
902
+ createWalletHook,
903
+ TransactionPoller,
904
+ TransactionMonitor,
905
+ DevTools,
906
+ PerformanceMonitor
907
+ };