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