@zendfi/sdk 0.8.4 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -437
- package/dist/chunk-DAJL2Q36.mjs +907 -0
- package/dist/express.d.mts +1 -1
- package/dist/express.d.ts +1 -1
- package/dist/helpers/index.d.mts +402 -0
- package/dist/helpers/index.d.ts +402 -0
- package/dist/helpers/index.js +944 -0
- package/dist/helpers/index.mjs +17 -0
- package/dist/index.d.mts +468 -3121
- package/dist/index.d.ts +468 -3121
- package/dist/index.js +421 -4020
- package/dist/index.mjs +1188 -5267
- package/dist/nextjs.d.mts +1 -1
- package/dist/nextjs.d.ts +1 -1
- package/dist/webhook-handler-61UWBtDI.d.mts +339 -0
- package/dist/webhook-handler-61UWBtDI.d.ts +339 -0
- package/package.json +19 -15
- package/README.md.old +0 -326
- package/dist/cache-T5YPC7OK.mjs +0 -9
- package/dist/chunk-5O5NAX65.mjs +0 -366
- package/dist/webhook-handler-CdtQHVU5.d.mts +0 -1130
- package/dist/webhook-handler-CdtQHVU5.d.ts +0 -1130
|
@@ -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
|
+
};
|