@0xio/sdk 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1745 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 0xio Wallet SDK - Event System
5
+ * Type-safe event emitter for wallet events
6
+ */
7
+ class EventEmitter {
8
+ constructor(debug = false) {
9
+ this.listeners = new Map();
10
+ this.debug = debug;
11
+ }
12
+ /**
13
+ * Add event listener
14
+ */
15
+ on(eventType, listener) {
16
+ if (!this.listeners.has(eventType)) {
17
+ this.listeners.set(eventType, new Set());
18
+ }
19
+ this.listeners.get(eventType).add(listener);
20
+ if (this.debug) ;
21
+ }
22
+ /**
23
+ * Remove event listener
24
+ */
25
+ off(eventType, listener) {
26
+ const eventListeners = this.listeners.get(eventType);
27
+ if (eventListeners) {
28
+ eventListeners.delete(listener);
29
+ if (eventListeners.size === 0) {
30
+ this.listeners.delete(eventType);
31
+ }
32
+ if (this.debug) ;
33
+ }
34
+ }
35
+ /**
36
+ * Add one-time event listener
37
+ */
38
+ once(eventType, listener) {
39
+ const onceListener = (event) => {
40
+ listener(event);
41
+ this.off(eventType, onceListener);
42
+ };
43
+ this.on(eventType, onceListener);
44
+ }
45
+ /**
46
+ * Emit event to all listeners
47
+ */
48
+ emit(eventType, data) {
49
+ const event = {
50
+ type: eventType,
51
+ data,
52
+ timestamp: Date.now()
53
+ };
54
+ const eventListeners = this.listeners.get(eventType);
55
+ if (eventListeners && eventListeners.size > 0) {
56
+ if (this.debug) ;
57
+ // Create a copy to avoid issues if listeners modify the set during iteration
58
+ const listenersArray = Array.from(eventListeners);
59
+ for (const listener of listenersArray) {
60
+ try {
61
+ listener(event);
62
+ }
63
+ catch (error) {
64
+ // console.error(`[0xio SDK] Error in event listener for '${eventType}':`, error);
65
+ }
66
+ }
67
+ }
68
+ else if (this.debug) ;
69
+ }
70
+ /**
71
+ * Remove all listeners for a specific event type
72
+ */
73
+ removeAllListeners(eventType) {
74
+ if (eventType) {
75
+ this.listeners.delete(eventType);
76
+ if (this.debug) ;
77
+ }
78
+ else {
79
+ this.listeners.clear();
80
+ if (this.debug) ;
81
+ }
82
+ }
83
+ /**
84
+ * Get number of listeners for an event type
85
+ */
86
+ listenerCount(eventType) {
87
+ const eventListeners = this.listeners.get(eventType);
88
+ return eventListeners ? eventListeners.size : 0;
89
+ }
90
+ /**
91
+ * Get all event types that have listeners
92
+ */
93
+ eventTypes() {
94
+ return Array.from(this.listeners.keys());
95
+ }
96
+ /**
97
+ * Check if there are any listeners for an event type
98
+ */
99
+ hasListeners(eventType) {
100
+ return this.listenerCount(eventType) > 0;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * 0xio Wallet SDK - Type Definitions
106
+ * Comprehensive type system for 0xio wallet integration with Octra Network
107
+ */
108
+ // Error types
109
+ exports.ErrorCode = void 0;
110
+ (function (ErrorCode) {
111
+ ErrorCode["EXTENSION_NOT_FOUND"] = "EXTENSION_NOT_FOUND";
112
+ ErrorCode["CONNECTION_REFUSED"] = "CONNECTION_REFUSED";
113
+ ErrorCode["USER_REJECTED"] = "USER_REJECTED";
114
+ ErrorCode["INSUFFICIENT_BALANCE"] = "INSUFFICIENT_BALANCE";
115
+ ErrorCode["INVALID_ADDRESS"] = "INVALID_ADDRESS";
116
+ ErrorCode["INVALID_AMOUNT"] = "INVALID_AMOUNT";
117
+ ErrorCode["NETWORK_ERROR"] = "NETWORK_ERROR";
118
+ ErrorCode["TRANSACTION_FAILED"] = "TRANSACTION_FAILED";
119
+ ErrorCode["PERMISSION_DENIED"] = "PERMISSION_DENIED";
120
+ ErrorCode["WALLET_LOCKED"] = "WALLET_LOCKED";
121
+ ErrorCode["RATE_LIMIT_EXCEEDED"] = "RATE_LIMIT_EXCEEDED";
122
+ ErrorCode["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
123
+ })(exports.ErrorCode || (exports.ErrorCode = {}));
124
+ class ZeroXIOWalletError extends Error {
125
+ constructor(code, message, details) {
126
+ super(message);
127
+ this.code = code;
128
+ this.details = details;
129
+ this.name = 'ZeroXIOWalletError';
130
+ }
131
+ }
132
+
133
+ /**
134
+ * 0xio Wallet SDK - Utilities
135
+ * Helper functions for validation, formatting, and common operations
136
+ */
137
+ // ===================
138
+ // VALIDATION UTILITIES
139
+ // ===================
140
+ /**
141
+ * Validate wallet address for Octra blockchain
142
+ */
143
+ function isValidAddress(address) {
144
+ if (!address || typeof address !== 'string') {
145
+ return false;
146
+ }
147
+ // Octra addresses should match the expected format
148
+ // This is a basic validation - adjust based on actual Octra address format
149
+ const addressRegex = /^[A-Za-z0-9]{20,64}$/;
150
+ return addressRegex.test(address);
151
+ }
152
+ /**
153
+ * Validate transaction amount
154
+ */
155
+ function isValidAmount(amount) {
156
+ return typeof amount === 'number' &&
157
+ amount > 0 &&
158
+ Number.isFinite(amount) &&
159
+ amount <= Number.MAX_SAFE_INTEGER;
160
+ }
161
+ /**
162
+ * Validate network ID
163
+ */
164
+ function isValidNetworkId(networkId) {
165
+ if (!networkId || typeof networkId !== 'string') {
166
+ return false;
167
+ }
168
+ const validNetworks = ['mainnet', 'testnet', 'devnet'];
169
+ return validNetworks.includes(networkId.toLowerCase());
170
+ }
171
+ /**
172
+ * Validate transaction message
173
+ */
174
+ function isValidMessage(message) {
175
+ if (!message) {
176
+ return true; // Empty messages are valid
177
+ }
178
+ if (typeof message !== 'string') {
179
+ return false;
180
+ }
181
+ // Check length (adjust based on network limits)
182
+ return message.length <= 280;
183
+ }
184
+ /**
185
+ * Validate fee level
186
+ */
187
+ function isValidFeeLevel(feeLevel) {
188
+ return feeLevel === 1 || feeLevel === 3;
189
+ }
190
+ // ===================
191
+ // FORMATTING UTILITIES
192
+ // ===================
193
+ /**
194
+ * Format OCT amount for display
195
+ */
196
+ function formatOCT(amount, decimals = 6) {
197
+ if (!isValidAmount(amount)) {
198
+ return '0';
199
+ }
200
+ return amount.toLocaleString(undefined, {
201
+ minimumFractionDigits: 0,
202
+ maximumFractionDigits: decimals
203
+ });
204
+ }
205
+ /**
206
+ * Format address for display (truncated)
207
+ */
208
+ function formatAddress(address, prefixLength = 6, suffixLength = 4) {
209
+ if (!isValidAddress(address)) {
210
+ return 'Invalid Address';
211
+ }
212
+ if (address.length <= prefixLength + suffixLength) {
213
+ return address;
214
+ }
215
+ return `${address.slice(0, prefixLength)}...${address.slice(-suffixLength)}`;
216
+ }
217
+ /**
218
+ * Format timestamp for display
219
+ */
220
+ function formatTimestamp(timestamp) {
221
+ const date = new Date(timestamp);
222
+ return date.toLocaleString();
223
+ }
224
+ /**
225
+ * Format transaction hash for display
226
+ */
227
+ function formatTxHash(hash, length = 12) {
228
+ if (!hash || typeof hash !== 'string') {
229
+ return 'Invalid Hash';
230
+ }
231
+ if (hash.length <= length) {
232
+ return hash;
233
+ }
234
+ const prefixLength = Math.ceil(length / 2);
235
+ const suffixLength = Math.floor(length / 2);
236
+ return `${hash.slice(0, prefixLength)}...${hash.slice(-suffixLength)}`;
237
+ }
238
+ // ===================
239
+ // CONVERSION UTILITIES
240
+ // ===================
241
+ /**
242
+ * Convert OCT to micro OCT (for network transmission)
243
+ */
244
+ function toMicroOCT(amount) {
245
+ if (!isValidAmount(amount)) {
246
+ throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_AMOUNT, 'Invalid amount for conversion');
247
+ }
248
+ // Assuming OCT has 6 decimal places like many cryptocurrencies
249
+ const microOCT = Math.round(amount * 1000000);
250
+ return microOCT.toString();
251
+ }
252
+ /**
253
+ * Convert micro OCT to OCT (for display)
254
+ */
255
+ function fromMicroOCT(microAmount) {
256
+ const amount = typeof microAmount === 'string' ? parseInt(microAmount, 10) : microAmount;
257
+ if (!Number.isFinite(amount) || amount < 0) {
258
+ throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_AMOUNT, 'Invalid micro OCT amount for conversion');
259
+ }
260
+ return amount / 1000000;
261
+ }
262
+ // ===================
263
+ // ERROR UTILITIES
264
+ // ===================
265
+ /**
266
+ * Create standardized error messages
267
+ */
268
+ function createErrorMessage(code, context) {
269
+ const baseMessages = {
270
+ [exports.ErrorCode.EXTENSION_NOT_FOUND]: '0xio Wallet extension is not installed or enabled',
271
+ [exports.ErrorCode.CONNECTION_REFUSED]: 'Connection to wallet was refused',
272
+ [exports.ErrorCode.USER_REJECTED]: 'User rejected the request',
273
+ [exports.ErrorCode.INSUFFICIENT_BALANCE]: 'Insufficient balance for this transaction',
274
+ [exports.ErrorCode.INVALID_ADDRESS]: 'Invalid wallet address provided',
275
+ [exports.ErrorCode.INVALID_AMOUNT]: 'Invalid transaction amount',
276
+ [exports.ErrorCode.NETWORK_ERROR]: 'Network communication error',
277
+ [exports.ErrorCode.TRANSACTION_FAILED]: 'Transaction failed to process',
278
+ [exports.ErrorCode.PERMISSION_DENIED]: 'Permission denied for this operation',
279
+ [exports.ErrorCode.WALLET_LOCKED]: 'Wallet is locked, please unlock first',
280
+ [exports.ErrorCode.RATE_LIMIT_EXCEEDED]: 'Rate limit exceeded, please try again later',
281
+ [exports.ErrorCode.UNKNOWN_ERROR]: 'An unknown error occurred'
282
+ };
283
+ const baseMessage = baseMessages[code] || 'Unknown error';
284
+ return context ? `${baseMessage}: ${context}` : baseMessage;
285
+ }
286
+ /**
287
+ * Check if error is a specific type
288
+ */
289
+ function isErrorType(error, code) {
290
+ return error instanceof ZeroXIOWalletError && error.code === code;
291
+ }
292
+ // ===================
293
+ // ASYNC UTILITIES
294
+ // ===================
295
+ /**
296
+ * Create a promise that resolves after a delay
297
+ */
298
+ function delay(ms) {
299
+ return new Promise(resolve => setTimeout(resolve, ms));
300
+ }
301
+ /**
302
+ * Retry an async operation with exponential backoff
303
+ */
304
+ async function retry(operation, maxRetries = 3, baseDelay = 1000) {
305
+ let lastError;
306
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
307
+ try {
308
+ return await operation();
309
+ }
310
+ catch (error) {
311
+ lastError = error;
312
+ if (attempt === maxRetries) {
313
+ break; // Last attempt failed
314
+ }
315
+ // Exponential backoff: 1s, 2s, 4s, etc.
316
+ const delayMs = baseDelay * Math.pow(2, attempt);
317
+ await delay(delayMs);
318
+ }
319
+ }
320
+ throw lastError;
321
+ }
322
+ /**
323
+ * Timeout wrapper for promises
324
+ */
325
+ function withTimeout(promise, timeoutMs, timeoutMessage = 'Operation timed out') {
326
+ const timeoutPromise = new Promise((_, reject) => {
327
+ setTimeout(() => {
328
+ reject(new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, timeoutMessage));
329
+ }, timeoutMs);
330
+ });
331
+ return Promise.race([promise, timeoutPromise]);
332
+ }
333
+ // ===================
334
+ // BROWSER UTILITIES
335
+ // ===================
336
+ /**
337
+ * Check if running in browser environment
338
+ */
339
+ function isBrowser() {
340
+ return typeof window !== 'undefined' && typeof document !== 'undefined';
341
+ }
342
+ /**
343
+ * Check if browser supports required features
344
+ */
345
+ function checkBrowserSupport() {
346
+ const missingFeatures = [];
347
+ if (!isBrowser()) {
348
+ missingFeatures.push('Browser environment');
349
+ return { supported: false, missingFeatures };
350
+ }
351
+ // Check for required browser APIs
352
+ if (typeof window.postMessage !== 'function') {
353
+ missingFeatures.push('PostMessage API');
354
+ }
355
+ if (typeof window.addEventListener !== 'function') {
356
+ missingFeatures.push('Event Listeners');
357
+ }
358
+ if (typeof Promise === 'undefined') {
359
+ missingFeatures.push('Promise support');
360
+ }
361
+ return {
362
+ supported: missingFeatures.length === 0,
363
+ missingFeatures
364
+ };
365
+ }
366
+ // ===================
367
+ // DEVELOPMENT UTILITIES
368
+ // ===================
369
+ /**
370
+ * Generate mock data for development/testing
371
+ */
372
+ function generateMockData() {
373
+ return {
374
+ address: 'OCT' + Math.random().toString(36).substr(2, 20).toUpperCase(),
375
+ balance: {
376
+ public: Math.floor(Math.random() * 10000),
377
+ private: Math.floor(Math.random() * 1000),
378
+ total: 0,
379
+ currency: 'OCT'
380
+ },
381
+ networkInfo: {
382
+ id: 'testnet',
383
+ name: 'Octra Testnet',
384
+ rpcUrl: 'https://testnet.octra.network',
385
+ color: '#f59e0b',
386
+ isTestnet: true
387
+ }
388
+ };
389
+ }
390
+ /**
391
+ * Create development logger
392
+ */
393
+ function createLogger(prefix, debug) {
394
+ const isDevelopment = typeof window !== 'undefined' && (window.location.hostname === 'localhost' ||
395
+ window.location.hostname === '127.0.0.1' ||
396
+ window.location.hostname.includes('dev') ||
397
+ window.__OCTRA_SDK_DEBUG__);
398
+ // Only enable logging in development mode AND when debug is explicitly enabled
399
+ const shouldLog = debug && isDevelopment;
400
+ return {
401
+ log: (...args) => {
402
+ if (shouldLog) {
403
+ console.log(`[${prefix}]`, ...args);
404
+ }
405
+ },
406
+ warn: (...args) => {
407
+ if (shouldLog) {
408
+ console.warn(`[${prefix}]`, ...args);
409
+ }
410
+ },
411
+ error: (...args) => {
412
+ // Always show errors in development, even without debug flag
413
+ if (isDevelopment) {
414
+ console.error(`[${prefix}]`, ...args);
415
+ }
416
+ },
417
+ debug: (...args) => {
418
+ if (shouldLog) {
419
+ console.debug(`[${prefix}]`, ...args);
420
+ }
421
+ },
422
+ table: (data) => {
423
+ if (shouldLog) {
424
+ console.log(`[${prefix}] Table data:`);
425
+ console.table(data);
426
+ }
427
+ },
428
+ group: (label) => {
429
+ if (shouldLog) {
430
+ console.group(`[${prefix}] ${label}`);
431
+ }
432
+ },
433
+ groupEnd: () => {
434
+ if (shouldLog) {
435
+ console.groupEnd();
436
+ }
437
+ }
438
+ };
439
+ }
440
+
441
+ /**
442
+ * 0xio Wallet SDK - Extension Communication Module
443
+ *
444
+ * @fileoverview Manages secure communication between the SDK and browser extension.
445
+ * Implements message passing, request/response handling, rate limiting, and origin validation
446
+ * to ensure secure wallet interactions.
447
+ *
448
+ * @module communication
449
+ * @version 1.2.0
450
+ * @license MIT
451
+ */
452
+ /**
453
+ * ExtensionCommunicator - Manages communication with the 0xio Wallet browser extension
454
+ *
455
+ * @class
456
+ * @extends EventEmitter
457
+ *
458
+ * @description
459
+ * Handles all communication between the SDK and wallet extension including:
460
+ * - Request/response message passing with origin validation
461
+ * - Rate limiting to prevent DoS attacks
462
+ * - Automatic retry logic with exponential backoff
463
+ * - Extension detection and availability monitoring
464
+ * - Cryptographically secure request ID generation
465
+ *
466
+ * @example
467
+ * ```typescript
468
+ * const communicator = new ExtensionCommunicator(true); // debug mode
469
+ * await communicator.initialize();
470
+ *
471
+ * const response = await communicator.sendRequest('get_balance', {});
472
+ * console.log(response);
473
+ * ```
474
+ */
475
+ class ExtensionCommunicator extends EventEmitter {
476
+ /**
477
+ * Creates a new ExtensionCommunicator instance
478
+ *
479
+ * @param {boolean} debug - Enable debug logging
480
+ */
481
+ constructor(debug = false) {
482
+ super(debug);
483
+ /** Legacy request counter (deprecated, kept for fallback) */
484
+ this.requestId = 0;
485
+ /** Map of pending requests awaiting responses */
486
+ this.pendingRequests = new Map();
487
+ /** Initialization state flag */
488
+ this.isInitialized = false;
489
+ /** Interval handle for periodic extension detection */
490
+ this.extensionDetectionInterval = null;
491
+ /** Current extension availability state */
492
+ this.isExtensionAvailableState = false;
493
+ // Rate limiting configuration
494
+ /** Maximum number of concurrent pending requests */
495
+ this.MAX_CONCURRENT_REQUESTS = 50;
496
+ /** Time window for rate limiting (milliseconds) */
497
+ this.RATE_LIMIT_WINDOW = 1000;
498
+ /** Maximum requests allowed per time window */
499
+ this.MAX_REQUESTS_PER_WINDOW = 20;
500
+ /** Timestamps of recent requests for rate limiting */
501
+ this.requestTimestamps = [];
502
+ this.logger = createLogger('ExtensionCommunicator', debug);
503
+ this.setupMessageListener();
504
+ this.startExtensionDetection();
505
+ }
506
+ /**
507
+ * Initialize communication with the wallet extension
508
+ *
509
+ * @description
510
+ * Performs initial setup and verification:
511
+ * 1. Waits for extension to become available
512
+ * 2. Sends ping to verify communication
513
+ * 3. Establishes message handlers
514
+ *
515
+ * Must be called before any other methods.
516
+ *
517
+ * @returns {Promise<boolean>} True if initialization succeeded, false otherwise
518
+ * @throws {ZeroXIOWalletError} If extension is not available after timeout
519
+ *
520
+ * @example
521
+ * ```typescript
522
+ * const success = await communicator.initialize();
523
+ * if (!success) {
524
+ * console.error('Failed to initialize wallet connection');
525
+ * }
526
+ * ```
527
+ */
528
+ async initialize() {
529
+ if (this.isInitialized) {
530
+ return true;
531
+ }
532
+ try {
533
+ // Wait for extension detection with timeout
534
+ const available = await this.waitForExtensionAvailability(10000);
535
+ if (available) {
536
+ // Verify with ping
537
+ await withTimeout(this.sendRequestWithRetry('ping', {}, 3, 2000), 8000, 'Extension ping timeout during initialization');
538
+ this.isInitialized = true;
539
+ this.logger.log('Extension communication initialized successfully');
540
+ }
541
+ else {
542
+ this.logger.error('Extension not available after waiting');
543
+ }
544
+ return this.isInitialized;
545
+ }
546
+ catch (error) {
547
+ this.logger.error('Failed to initialize extension communication:', error);
548
+ return false;
549
+ }
550
+ }
551
+ /**
552
+ * Check if extension is available
553
+ */
554
+ isExtensionAvailable() {
555
+ return this.isExtensionAvailableState && this.hasExtensionContext();
556
+ }
557
+ /**
558
+ * Send request to extension
559
+ */
560
+ async sendRequest(method, params = {}, timeout = 30000) {
561
+ return this.sendRequestWithRetry(method, params, 1, timeout);
562
+ }
563
+ /**
564
+ * Send request to extension with automatic retry logic
565
+ */
566
+ async sendRequestWithRetry(method, params = {}, maxRetries = 3, timeout = 30000) {
567
+ if (!this.hasExtensionContext()) {
568
+ throw new ZeroXIOWalletError(exports.ErrorCode.EXTENSION_NOT_FOUND, '0xio Wallet extension is not installed or available', {
569
+ method,
570
+ params,
571
+ browserContext: this.getBrowserDiagnostics()
572
+ });
573
+ }
574
+ if (!this.isExtensionAvailableState) {
575
+ // Wait a bit for extension to become available
576
+ await this.waitForExtensionAvailability(5000);
577
+ if (!this.isExtensionAvailableState) {
578
+ throw new ZeroXIOWalletError(exports.ErrorCode.EXTENSION_NOT_FOUND, 'Extension not available for communication', {
579
+ method,
580
+ params,
581
+ extensionState: this.getExtensionDiagnostics()
582
+ });
583
+ }
584
+ }
585
+ // ✅ SECURITY: Check rate limits before processing
586
+ this.checkRateLimit();
587
+ return retry(async () => {
588
+ const requestId = this.generateRequestId();
589
+ const request = {
590
+ id: requestId,
591
+ method,
592
+ params,
593
+ timestamp: Date.now()
594
+ };
595
+ this.logger.log(`Sending request (${method}):`, { id: requestId, params });
596
+ return new Promise((resolve, reject) => {
597
+ // Set up timeout
598
+ const timeoutHandle = setTimeout(() => {
599
+ const pending = this.pendingRequests.get(requestId);
600
+ if (pending) {
601
+ this.pendingRequests.delete(requestId);
602
+ reject(new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, `Request timeout after ${timeout}ms`, {
603
+ method,
604
+ params,
605
+ requestId,
606
+ retryCount: pending.retryCount,
607
+ extensionState: this.getExtensionDiagnostics()
608
+ }));
609
+ }
610
+ }, timeout);
611
+ // Store request handlers
612
+ this.pendingRequests.set(requestId, {
613
+ resolve,
614
+ reject,
615
+ timeout: timeoutHandle,
616
+ retryCount: 0
617
+ });
618
+ // Send message to extension via content script bridge
619
+ this.postMessageToExtension(request);
620
+ });
621
+ }, maxRetries, 1000);
622
+ }
623
+ /**
624
+ * Setup message listener for responses from extension
625
+ */
626
+ setupMessageListener() {
627
+ if (typeof window === 'undefined') {
628
+ return; // Not in browser environment
629
+ }
630
+ const allowedOrigin = window.location.origin;
631
+ window.addEventListener('message', (event) => {
632
+ // ✅ SECURITY: Validate origin first - critical security check
633
+ if (event.origin !== allowedOrigin) {
634
+ this.logger.warn('Blocked message from untrusted origin:', event.origin);
635
+ return;
636
+ }
637
+ // Only accept messages from same window
638
+ if (event.source !== window) {
639
+ return;
640
+ }
641
+ // Check if it's a 0xio SDK response
642
+ if (!event.data || event.data.source !== '0xio-sdk-bridge') {
643
+ return;
644
+ }
645
+ // Handle different types of messages
646
+ if (event.data.response) {
647
+ // Regular request/response
648
+ const response = event.data.response;
649
+ if (response && response.id) {
650
+ this.handleExtensionResponse(response);
651
+ }
652
+ }
653
+ else if (event.data.event) {
654
+ // Event notification from extension
655
+ this.handleExtensionEvent(event.data.event);
656
+ }
657
+ });
658
+ this.logger.log('Message listener setup complete');
659
+ }
660
+ /**
661
+ * Handle extension event
662
+ */
663
+ handleExtensionEvent(event) {
664
+ this.logger.log('Received extension event:', event.type);
665
+ // Forward the event to listeners
666
+ this.emit(event.type, event.data);
667
+ }
668
+ /**
669
+ * Handle response from extension
670
+ */
671
+ handleExtensionResponse(response) {
672
+ this.logger.log(`Received response:`, { id: response.id, success: response.success });
673
+ const pending = this.pendingRequests.get(response.id);
674
+ if (!pending) {
675
+ this.logger.warn(`Received response for unknown request ID: ${response.id}`);
676
+ return;
677
+ }
678
+ // Clear timeout and remove from pending
679
+ clearTimeout(pending.timeout);
680
+ this.pendingRequests.delete(response.id);
681
+ // Handle response
682
+ if (response.success) {
683
+ pending.resolve(response.data);
684
+ }
685
+ else {
686
+ const error = response.error;
687
+ if (error) {
688
+ const enhancedError = new ZeroXIOWalletError(error.code, error.message, {
689
+ ...error.details,
690
+ requestId: response.id,
691
+ retryCount: pending.retryCount,
692
+ timestamp: Date.now(),
693
+ extensionState: this.getExtensionDiagnostics()
694
+ });
695
+ pending.reject(enhancedError);
696
+ }
697
+ else {
698
+ pending.reject(new ZeroXIOWalletError(exports.ErrorCode.UNKNOWN_ERROR, 'Unknown error occurred', {
699
+ requestId: response.id,
700
+ retryCount: pending.retryCount,
701
+ extensionState: this.getExtensionDiagnostics()
702
+ }));
703
+ }
704
+ }
705
+ }
706
+ /**
707
+ * Post message to extension via content script
708
+ */
709
+ postMessageToExtension(request) {
710
+ // ✅ SECURITY: Use specific origin instead of wildcard
711
+ const targetOrigin = window.location.origin;
712
+ window.postMessage({
713
+ source: '0xio-sdk-request',
714
+ request
715
+ }, targetOrigin);
716
+ }
717
+ /**
718
+ * Check if we're in a context that can communicate with extension
719
+ */
720
+ hasExtensionContext() {
721
+ return typeof window !== 'undefined' &&
722
+ typeof window.postMessage === 'function';
723
+ }
724
+ /**
725
+ * Check and enforce rate limits to prevent denial-of-service attacks
726
+ *
727
+ * @private
728
+ * @throws {ZeroXIOWalletError} RATE_LIMIT_EXCEEDED if limits are exceeded
729
+ *
730
+ * @description
731
+ * Implements two-tier rate limiting:
732
+ * 1. Concurrent requests: Maximum 50 pending requests at once
733
+ * 2. Request frequency: Maximum 20 requests per second
734
+ *
735
+ * Rate limiting protects both the SDK and extension from:
736
+ * - Accidental infinite loops in dApp code
737
+ * - Malicious DoS attacks
738
+ * - Resource exhaustion
739
+ *
740
+ * @security Critical security function - enforces resource limits
741
+ */
742
+ checkRateLimit() {
743
+ const now = Date.now();
744
+ // ✅ SECURITY: Check concurrent request limit
745
+ if (this.pendingRequests.size >= this.MAX_CONCURRENT_REQUESTS) {
746
+ throw new ZeroXIOWalletError(exports.ErrorCode.RATE_LIMIT_EXCEEDED, `Too many concurrent requests (max: ${this.MAX_CONCURRENT_REQUESTS})`);
747
+ }
748
+ // ✅ SECURITY: Check requests per time window
749
+ this.requestTimestamps = this.requestTimestamps.filter(t => now - t < this.RATE_LIMIT_WINDOW);
750
+ if (this.requestTimestamps.length >= this.MAX_REQUESTS_PER_WINDOW) {
751
+ throw new ZeroXIOWalletError(exports.ErrorCode.RATE_LIMIT_EXCEEDED, `Too many requests per second (max: ${this.MAX_REQUESTS_PER_WINDOW} per ${this.RATE_LIMIT_WINDOW}ms)`);
752
+ }
753
+ this.requestTimestamps.push(now);
754
+ }
755
+ /**
756
+ * Generate cryptographically secure unique request ID
757
+ *
758
+ * @private
759
+ * @returns {string} A unique, unpredictable request identifier
760
+ *
761
+ * @description
762
+ * Uses Web Crypto API for secure random ID generation:
763
+ * 1. Primary: crypto.randomUUID() - UUID v4 format
764
+ * 2. Fallback: crypto.getRandomValues() - 128-bit random hex
765
+ * 3. Last resort: timestamp + counter (logs warning)
766
+ *
767
+ * Security importance:
768
+ * - Prevents request ID prediction attacks
769
+ * - Mitigates replay attacks
770
+ * - Makes session hijacking more difficult
771
+ *
772
+ * @security Critical - IDs must be cryptographically unpredictable
773
+ */
774
+ generateRequestId() {
775
+ // ✅ SECURITY: Use crypto.randomUUID() for secure, unpredictable IDs
776
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
777
+ return `0xio-sdk-${crypto.randomUUID()}`;
778
+ }
779
+ // Fallback to crypto.getRandomValues for older browsers
780
+ if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
781
+ const array = new Uint8Array(16);
782
+ crypto.getRandomValues(array);
783
+ const hex = Array.from(array, b => b.toString(16).padStart(2, '0')).join('');
784
+ return `0xio-sdk-${hex}`;
785
+ }
786
+ // Last resort fallback (not recommended for production)
787
+ this.logger.warn('Crypto API not available, using less secure ID generation');
788
+ return `0xio-sdk-${++this.requestId}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
789
+ }
790
+ /**
791
+ * Start continuous extension detection
792
+ */
793
+ startExtensionDetection() {
794
+ if (typeof window === 'undefined')
795
+ return;
796
+ // Listen for extension ready event (instant detection)
797
+ window.addEventListener('0xioWalletReady', () => {
798
+ this.logger.log('Received 0xioWalletReady event');
799
+ this.isExtensionAvailableState = true;
800
+ });
801
+ // Also listen for legacy event names
802
+ window.addEventListener('wallet0xioReady', () => {
803
+ this.logger.log('Received wallet0xioReady event');
804
+ this.isExtensionAvailableState = true;
805
+ });
806
+ window.addEventListener('octraWalletReady', () => {
807
+ this.logger.log('Received octraWalletReady event');
808
+ this.isExtensionAvailableState = true;
809
+ });
810
+ // Initial check (in case extension was already injected)
811
+ this.checkExtensionAvailability();
812
+ // Set up periodic checks as fallback
813
+ this.extensionDetectionInterval = setInterval(() => {
814
+ this.checkExtensionAvailability();
815
+ }, 2000);
816
+ }
817
+ /**
818
+ * Check if extension is currently available
819
+ */
820
+ checkExtensionAvailability() {
821
+ const wasAvailable = this.isExtensionAvailableState;
822
+ // Basic checks for extension context
823
+ this.isExtensionAvailableState = this.hasExtensionContext() && this.detectExtensionSignals();
824
+ if (!wasAvailable && this.isExtensionAvailableState) {
825
+ this.logger.log('Extension became available');
826
+ }
827
+ else if (wasAvailable && !this.isExtensionAvailableState) {
828
+ this.logger.warn('Extension became unavailable');
829
+ }
830
+ }
831
+ /**
832
+ * Detect extension signals/indicators
833
+ */
834
+ detectExtensionSignals() {
835
+ if (typeof window === 'undefined')
836
+ return false;
837
+ // Check for extension-injected indicators
838
+ const win = window;
839
+ // Look for common extension indicators
840
+ return !!(win.wallet0xio ||
841
+ win.ZeroXIOWallet ||
842
+ win.__0XIO_EXTENSION__ ||
843
+ win.__OCTRA_EXTENSION__ ||
844
+ win.octraWallet ||
845
+ (win.chrome?.runtime?.id) ||
846
+ document.querySelector('meta[name="0xio-extension"]') ||
847
+ document.querySelector('meta[name="octra-extension"]') ||
848
+ document.querySelector('[data-0xio-extension]') ||
849
+ document.querySelector('[data-octra-extension]'));
850
+ }
851
+ /**
852
+ * Wait for extension to become available
853
+ */
854
+ async waitForExtensionAvailability(timeoutMs) {
855
+ if (this.isExtensionAvailableState) {
856
+ return true;
857
+ }
858
+ return new Promise((resolve) => {
859
+ let resolved = false;
860
+ const startTime = Date.now();
861
+ // Cleanup function
862
+ const cleanup = () => {
863
+ clearInterval(checkInterval);
864
+ window.removeEventListener('0xioWalletReady', onReady);
865
+ window.removeEventListener('wallet0xioReady', onReady);
866
+ window.removeEventListener('octraWalletReady', onReady);
867
+ };
868
+ // Event handler for instant resolution
869
+ const onReady = () => {
870
+ if (resolved)
871
+ return;
872
+ resolved = true;
873
+ this.isExtensionAvailableState = true;
874
+ cleanup();
875
+ resolve(true);
876
+ };
877
+ // Listen for extension ready events
878
+ window.addEventListener('0xioWalletReady', onReady);
879
+ window.addEventListener('wallet0xioReady', onReady);
880
+ window.addEventListener('octraWalletReady', onReady);
881
+ // Polling fallback with shorter interval
882
+ const checkInterval = setInterval(() => {
883
+ if (resolved)
884
+ return;
885
+ if (this.isExtensionAvailableState) {
886
+ resolved = true;
887
+ cleanup();
888
+ resolve(true);
889
+ return;
890
+ }
891
+ if (Date.now() - startTime >= timeoutMs) {
892
+ resolved = true;
893
+ cleanup();
894
+ resolve(false);
895
+ }
896
+ }, 100);
897
+ });
898
+ }
899
+ /**
900
+ * Get browser diagnostics for error reporting
901
+ */
902
+ getBrowserDiagnostics() {
903
+ if (typeof window === 'undefined') {
904
+ return { environment: 'non-browser' };
905
+ }
906
+ const win = window;
907
+ return {
908
+ userAgent: navigator.userAgent,
909
+ hasChrome: !!win.chrome,
910
+ hasChromeRuntime: !!(win.chrome?.runtime),
911
+ hasPostMessage: typeof window.postMessage === 'function',
912
+ origin: window.location?.origin,
913
+ extensionDetection: {
914
+ hasOctraExtension: !!win.__OCTRA_EXTENSION__,
915
+ hasZeroXIOWallet: !!win.octraWallet,
916
+ hasChromeRuntimeId: !!(win.chrome?.runtime?.id),
917
+ hasMetaTag: !!document.querySelector('meta[name=\"octra-extension\"]'),
918
+ hasDataAttribute: !!document.querySelector('[data-octra-extension]')
919
+ }
920
+ };
921
+ }
922
+ /**
923
+ * Get extension state diagnostics
924
+ */
925
+ getExtensionDiagnostics() {
926
+ return {
927
+ initialized: this.isInitialized,
928
+ available: this.isExtensionAvailableState,
929
+ pendingRequests: this.pendingRequests.size,
930
+ hasExtensionContext: this.hasExtensionContext(),
931
+ browserDiagnostics: this.getBrowserDiagnostics()
932
+ };
933
+ }
934
+ /**
935
+ * Cleanup pending requests
936
+ */
937
+ cleanup() {
938
+ if (this.extensionDetectionInterval) {
939
+ clearInterval(this.extensionDetectionInterval);
940
+ this.extensionDetectionInterval = null;
941
+ }
942
+ for (const [, pending] of this.pendingRequests) {
943
+ clearTimeout(pending.timeout);
944
+ pending.reject(new ZeroXIOWalletError(exports.ErrorCode.UNKNOWN_ERROR, 'SDK cleanup called'));
945
+ }
946
+ this.pendingRequests.clear();
947
+ this.isInitialized = false;
948
+ this.isExtensionAvailableState = false;
949
+ // Call parent cleanup
950
+ this.removeAllListeners();
951
+ this.logger.log('Communication cleanup complete');
952
+ }
953
+ /**
954
+ * Get debug information
955
+ */
956
+ getDebugInfo() {
957
+ return {
958
+ initialized: this.isInitialized,
959
+ available: this.isExtensionAvailableState,
960
+ pendingRequests: this.pendingRequests.size,
961
+ hasExtensionContext: this.hasExtensionContext(),
962
+ extensionDiagnostics: this.getExtensionDiagnostics()
963
+ };
964
+ }
965
+ }
966
+
967
+ /**
968
+ * Network configuration for 0xio SDK
969
+ */
970
+ const NETWORKS = {
971
+ 'mainnet': {
972
+ id: 'mainnet',
973
+ name: 'Octra Mainnet Alpha',
974
+ rpcUrl: 'https://octra.network',
975
+ explorerUrl: 'https://octrascan.io/',
976
+ color: '#6366f1',
977
+ isTestnet: false
978
+ },
979
+ 'custom': {
980
+ id: 'custom',
981
+ name: 'Custom Network',
982
+ rpcUrl: '',
983
+ explorerUrl: '',
984
+ color: '#64748b',
985
+ isTestnet: false // User configurable - can be mainnet or testnet
986
+ }
987
+ };
988
+ const DEFAULT_NETWORK_ID = 'mainnet';
989
+ /**
990
+ * Get network configuration by ID
991
+ */
992
+ function getNetworkConfig(networkId = DEFAULT_NETWORK_ID) {
993
+ const network = NETWORKS[networkId];
994
+ if (!network) {
995
+ throw new Error(`Unknown network ID: ${networkId}`);
996
+ }
997
+ return network;
998
+ }
999
+ /**
1000
+ * Get all available networks
1001
+ */
1002
+ function getAllNetworks() {
1003
+ return Object.values(NETWORKS);
1004
+ }
1005
+
1006
+ /**
1007
+ * SDK Configuration
1008
+ */
1009
+ /**
1010
+ * Default balance structure
1011
+ */
1012
+ function createDefaultBalance(total = 0) {
1013
+ return {
1014
+ total,
1015
+ public: total,
1016
+ private: 0,
1017
+ currency: 'OCT'
1018
+ };
1019
+ }
1020
+ /**
1021
+ * SDK Configuration constants
1022
+ */
1023
+ const SDK_CONFIG = {
1024
+ version: '2.1.2',
1025
+ defaultNetworkId: DEFAULT_NETWORK_ID,
1026
+ communicationTimeout: 30000, // 30 seconds
1027
+ retryAttempts: 3,
1028
+ retryDelay: 1000, // 1 second
1029
+ };
1030
+ /**
1031
+ * Get default network configuration
1032
+ */
1033
+ function getDefaultNetwork() {
1034
+ return getNetworkConfig(SDK_CONFIG.defaultNetworkId);
1035
+ }
1036
+
1037
+ /**
1038
+ * 0xio Wallet SDK - Main Wallet Class
1039
+ * Primary interface for DApp developers to interact with 0xio Wallet
1040
+ */
1041
+ class ZeroXIOWallet extends EventEmitter {
1042
+ constructor(config) {
1043
+ super(config.debug);
1044
+ this.connectionInfo = { isConnected: false };
1045
+ this.isInitialized = false;
1046
+ this.config = {
1047
+ ...config,
1048
+ appVersion: config.appVersion || '1.0.0',
1049
+ requiredPermissions: config.requiredPermissions || ['read_balance'],
1050
+ debug: config.debug || false
1051
+ };
1052
+ this.logger = createLogger('ZeroXIOWallet', this.config.debug || false);
1053
+ this.communicator = new ExtensionCommunicator(this.config.debug);
1054
+ this.logger.log('Wallet instance created with config:', this.config);
1055
+ }
1056
+ // ===================
1057
+ // INITIALIZATION
1058
+ // ===================
1059
+ /**
1060
+ * Initialize the SDK
1061
+ * Must be called before using any other methods
1062
+ */
1063
+ async initialize() {
1064
+ if (this.isInitialized) {
1065
+ return true;
1066
+ }
1067
+ try {
1068
+ // Initialize extension communication
1069
+ const communicationReady = await this.communicator.initialize();
1070
+ if (!communicationReady) {
1071
+ throw new ZeroXIOWalletError(exports.ErrorCode.EXTENSION_NOT_FOUND, 'Failed to establish communication with 0xio Wallet extension');
1072
+ }
1073
+ // Register this DApp with the extension
1074
+ await this.communicator.sendRequest('register_dapp', {
1075
+ appName: this.config.appName,
1076
+ appDescription: this.config.appDescription,
1077
+ appVersion: this.config.appVersion,
1078
+ appUrl: typeof window !== 'undefined' ? window.location.origin : undefined,
1079
+ appIcon: this.config.appIcon,
1080
+ requiredPermissions: this.config.requiredPermissions,
1081
+ networkId: this.config.networkId
1082
+ });
1083
+ // Setup event forwarding from extension
1084
+ this.setupExtensionEventListeners();
1085
+ this.isInitialized = true;
1086
+ this.logger.log('SDK initialized successfully');
1087
+ return true;
1088
+ }
1089
+ catch (error) {
1090
+ this.logger.error('Failed to initialize:', error);
1091
+ if (error instanceof ZeroXIOWalletError) {
1092
+ throw error;
1093
+ }
1094
+ throw new ZeroXIOWalletError(exports.ErrorCode.UNKNOWN_ERROR, 'Failed to initialize SDK', error);
1095
+ }
1096
+ }
1097
+ /**
1098
+ * Check if SDK is initialized
1099
+ */
1100
+ isReady() {
1101
+ return this.isInitialized && this.communicator.isExtensionAvailable();
1102
+ }
1103
+ // ===================
1104
+ // CONNECTION MANAGEMENT
1105
+ // ===================
1106
+ /**
1107
+ * Connect to wallet
1108
+ */
1109
+ async connect(options = {}) {
1110
+ this.ensureInitialized();
1111
+ try {
1112
+ this.logger.log('Attempting to connect with options:', options);
1113
+ const result = await this.communicator.sendRequest('connect', {
1114
+ requestPermissions: options.requestPermissions || this.config.requiredPermissions,
1115
+ networkId: options.networkId || this.config.networkId
1116
+ });
1117
+ // Update connection info
1118
+ this.connectionInfo = {
1119
+ isConnected: true,
1120
+ address: result.address,
1121
+ balance: result.balance,
1122
+ networkInfo: result.networkInfo,
1123
+ connectedAt: Date.now()
1124
+ };
1125
+ const connectEvent = {
1126
+ address: result.address,
1127
+ balance: result.balance,
1128
+ networkInfo: result.networkInfo,
1129
+ permissions: result.permissions
1130
+ };
1131
+ // Emit connect event
1132
+ this.emit('connect', connectEvent);
1133
+ this.logger.log('Connected successfully:', connectEvent);
1134
+ return connectEvent;
1135
+ }
1136
+ catch (error) {
1137
+ this.logger.error('Connection failed:', error);
1138
+ if (error instanceof ZeroXIOWalletError) {
1139
+ throw error;
1140
+ }
1141
+ throw new ZeroXIOWalletError(exports.ErrorCode.CONNECTION_REFUSED, 'Failed to connect to wallet', error);
1142
+ }
1143
+ }
1144
+ /**
1145
+ * Disconnect from wallet
1146
+ */
1147
+ async disconnect() {
1148
+ this.ensureInitialized();
1149
+ try {
1150
+ await this.communicator.sendRequest('disconnect');
1151
+ this.connectionInfo = { isConnected: false };
1152
+ const disconnectEvent = {
1153
+ reason: 'user_action'
1154
+ };
1155
+ this.emit('disconnect', disconnectEvent);
1156
+ this.logger.log('Disconnected from wallet');
1157
+ }
1158
+ catch (error) {
1159
+ this.logger.error('Disconnect failed:', error);
1160
+ throw error;
1161
+ }
1162
+ }
1163
+ /**
1164
+ * Check if connected to wallet
1165
+ */
1166
+ isConnected() {
1167
+ return this.connectionInfo.isConnected;
1168
+ }
1169
+ /**
1170
+ * Get current connection info
1171
+ */
1172
+ getConnectionInfo() {
1173
+ return { ...this.connectionInfo };
1174
+ }
1175
+ /**
1176
+ * Check connection status with extension
1177
+ */
1178
+ async getConnectionStatus() {
1179
+ this.ensureInitialized();
1180
+ try {
1181
+ const result = await this.communicator.sendRequest('getConnectionStatus');
1182
+ if (result.isConnected && result.address) {
1183
+ // Update internal state if we discover an existing connection
1184
+ const balanceInfo = createDefaultBalance(result.balance);
1185
+ const networkInfo = getNetworkConfig(this.config.networkId);
1186
+ this.connectionInfo = {
1187
+ isConnected: true,
1188
+ address: result.address,
1189
+ balance: balanceInfo,
1190
+ networkInfo,
1191
+ connectedAt: result.connectedAt || Date.now()
1192
+ };
1193
+ this.logger.log('Discovered existing connection:', this.connectionInfo);
1194
+ // Emit connect event to notify the wrapper
1195
+ const connectEvent = {
1196
+ address: result.address,
1197
+ balance: balanceInfo,
1198
+ networkInfo,
1199
+ permissions: result.permissions || []
1200
+ };
1201
+ this.emit('connect', connectEvent);
1202
+ }
1203
+ else {
1204
+ // No existing connection
1205
+ this.connectionInfo = { isConnected: false };
1206
+ }
1207
+ return { ...this.connectionInfo };
1208
+ }
1209
+ catch (error) {
1210
+ this.logger.error('Failed to get connection status:', error);
1211
+ this.connectionInfo = { isConnected: false };
1212
+ return { ...this.connectionInfo };
1213
+ }
1214
+ }
1215
+ // ===================
1216
+ // WALLET INFORMATION
1217
+ // ===================
1218
+ /**
1219
+ * Get current wallet address
1220
+ */
1221
+ getAddress() {
1222
+ return this.connectionInfo.address || null;
1223
+ }
1224
+ /**
1225
+ * Get current balance
1226
+ */
1227
+ async getBalance(forceRefresh = false) {
1228
+ this.ensureConnected();
1229
+ try {
1230
+ const address = this.getAddress();
1231
+ if (!address) {
1232
+ throw new Error('No address found');
1233
+ }
1234
+ // Fetch balance from extension (bypasses CORS, has access to private balance)
1235
+ let publicBalance = 0;
1236
+ let privateBalance = 0;
1237
+ const extResult = await this.communicator.sendRequest('getBalance', { forceRefresh });
1238
+ publicBalance = parseFloat(extResult.balance || '0');
1239
+ privateBalance = parseFloat(extResult.privateBalance || '0');
1240
+ this.logger.log('Balance fetched from extension:', { public: publicBalance, private: privateBalance });
1241
+ const result = {
1242
+ public: publicBalance,
1243
+ private: privateBalance,
1244
+ total: publicBalance + privateBalance,
1245
+ currency: 'OCT'
1246
+ };
1247
+ // Update cached balance
1248
+ if (this.connectionInfo.balance) {
1249
+ const previousBalance = this.connectionInfo.balance;
1250
+ this.connectionInfo.balance = result;
1251
+ // Emit balance changed event if different
1252
+ if (previousBalance.total !== result.total) {
1253
+ const balanceChangedEvent = {
1254
+ address: this.connectionInfo.address,
1255
+ previousBalance,
1256
+ newBalance: result
1257
+ };
1258
+ this.emit('balanceChanged', balanceChangedEvent);
1259
+ }
1260
+ }
1261
+ else {
1262
+ this.connectionInfo.balance = result;
1263
+ }
1264
+ return result;
1265
+ }
1266
+ catch (error) {
1267
+ throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Failed to get balance', error);
1268
+ }
1269
+ }
1270
+ /**
1271
+ * Get network information
1272
+ */
1273
+ async getNetworkInfo() {
1274
+ this.ensureInitialized();
1275
+ try {
1276
+ const result = await this.communicator.sendRequest('get_network_info');
1277
+ // Update cached network info
1278
+ if (this.connectionInfo.networkInfo) {
1279
+ const previousNetwork = this.connectionInfo.networkInfo;
1280
+ this.connectionInfo.networkInfo = result;
1281
+ // Emit network changed event if different
1282
+ if (previousNetwork.id !== result.id) {
1283
+ const networkChangedEvent = {
1284
+ previousNetwork,
1285
+ newNetwork: result
1286
+ };
1287
+ this.emit('networkChanged', networkChangedEvent);
1288
+ }
1289
+ }
1290
+ else {
1291
+ this.connectionInfo.networkInfo = result;
1292
+ }
1293
+ return result;
1294
+ }
1295
+ catch (error) {
1296
+ throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Failed to get network info', error);
1297
+ }
1298
+ }
1299
+ // ===================
1300
+ // TRANSACTIONS
1301
+ // ===================
1302
+ /**
1303
+ * Send transaction
1304
+ */
1305
+ async sendTransaction(txData) {
1306
+ this.ensureConnected();
1307
+ try {
1308
+ this.logger.log('Sending transaction:', txData);
1309
+ const result = await this.communicator.sendRequest('send_transaction', txData);
1310
+ this.logger.log('Transaction result:', result);
1311
+ // Refresh balance after successful transaction
1312
+ if (result.success) {
1313
+ setTimeout(() => {
1314
+ this.getBalance(true).catch(error => {
1315
+ this.logger.warn('Failed to refresh balance after transaction:', error);
1316
+ });
1317
+ }, 1000);
1318
+ }
1319
+ return result;
1320
+ }
1321
+ catch (error) {
1322
+ this.logger.error('Transaction failed:', error);
1323
+ if (error instanceof ZeroXIOWalletError) {
1324
+ throw error;
1325
+ }
1326
+ throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Failed to send transaction', error);
1327
+ }
1328
+ }
1329
+ /**
1330
+ * Get transaction history
1331
+ */
1332
+ async getTransactionHistory(page = 1, limit = 20) {
1333
+ this.ensureConnected();
1334
+ try {
1335
+ const result = await this.communicator.sendRequest('get_transaction_history', {
1336
+ page,
1337
+ limit
1338
+ });
1339
+ return result;
1340
+ }
1341
+ catch (error) {
1342
+ throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Failed to get transaction history', error);
1343
+ }
1344
+ }
1345
+ // ===================
1346
+ // PRIVATE FEATURES
1347
+ // ===================
1348
+ /**
1349
+ * Get private balance information
1350
+ */
1351
+ async getPrivateBalanceInfo() {
1352
+ this.ensureConnected();
1353
+ try {
1354
+ const result = await this.communicator.sendRequest('get_private_balance_info');
1355
+ return result;
1356
+ }
1357
+ catch (error) {
1358
+ throw new ZeroXIOWalletError(exports.ErrorCode.PERMISSION_DENIED, 'Failed to get private balance info', error);
1359
+ }
1360
+ }
1361
+ /**
1362
+ * Encrypt public balance to private
1363
+ */
1364
+ async encryptBalance(amount) {
1365
+ this.ensureConnected();
1366
+ try {
1367
+ const result = await this.communicator.sendRequest('encrypt_balance', { amount });
1368
+ // Refresh balance after encryption
1369
+ setTimeout(() => {
1370
+ this.getBalance(true).catch(() => { });
1371
+ }, 1000);
1372
+ return result.success;
1373
+ }
1374
+ catch (error) {
1375
+ throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Failed to encrypt balance', error);
1376
+ }
1377
+ }
1378
+ /**
1379
+ * Decrypt private balance to public
1380
+ */
1381
+ async decryptBalance(amount) {
1382
+ this.ensureConnected();
1383
+ try {
1384
+ const result = await this.communicator.sendRequest('decrypt_balance', { amount });
1385
+ // Refresh balance after decryption
1386
+ setTimeout(() => {
1387
+ this.getBalance(true).catch(() => { });
1388
+ }, 1000);
1389
+ return result.success;
1390
+ }
1391
+ catch (error) {
1392
+ throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Failed to decrypt balance', error);
1393
+ }
1394
+ }
1395
+ /**
1396
+ * Send private transfer
1397
+ */
1398
+ async sendPrivateTransfer(transferData) {
1399
+ this.ensureConnected();
1400
+ try {
1401
+ const result = await this.communicator.sendRequest('send_private_transfer', transferData);
1402
+ // Refresh balance after transfer
1403
+ if (result.success) {
1404
+ setTimeout(() => {
1405
+ this.getBalance(true).catch(() => { });
1406
+ }, 1000);
1407
+ }
1408
+ return result;
1409
+ }
1410
+ catch (error) {
1411
+ throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Failed to send private transfer', error);
1412
+ }
1413
+ }
1414
+ /**
1415
+ * Get pending private transfers
1416
+ */
1417
+ async getPendingPrivateTransfers() {
1418
+ this.ensureConnected();
1419
+ try {
1420
+ const result = await this.communicator.sendRequest('get_pending_private_transfers');
1421
+ return result;
1422
+ }
1423
+ catch (error) {
1424
+ throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Failed to get pending private transfers', error);
1425
+ }
1426
+ }
1427
+ /**
1428
+ * Claim private transfer
1429
+ */
1430
+ async claimPrivateTransfer(transferId) {
1431
+ this.ensureConnected();
1432
+ try {
1433
+ const result = await this.communicator.sendRequest('claim_private_transfer', {
1434
+ transferId
1435
+ });
1436
+ // Refresh balance after claiming
1437
+ if (result.success) {
1438
+ setTimeout(() => {
1439
+ this.getBalance(true).catch(() => { });
1440
+ }, 1000);
1441
+ }
1442
+ return result;
1443
+ }
1444
+ catch (error) {
1445
+ throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Failed to claim private transfer', error);
1446
+ }
1447
+ }
1448
+ // ===================
1449
+ // PRIVATE METHODS
1450
+ // ===================
1451
+ ensureInitialized() {
1452
+ if (!this.isInitialized) {
1453
+ throw new ZeroXIOWalletError(exports.ErrorCode.UNKNOWN_ERROR, 'SDK not initialized. Call initialize() first.');
1454
+ }
1455
+ }
1456
+ ensureConnected() {
1457
+ this.ensureInitialized();
1458
+ if (!this.connectionInfo.isConnected) {
1459
+ throw new ZeroXIOWalletError(exports.ErrorCode.CONNECTION_REFUSED, 'Wallet not connected. Call connect() first.');
1460
+ }
1461
+ }
1462
+ setupExtensionEventListeners() {
1463
+ // Listen for extension events through the communicator
1464
+ this.communicator.on('accountChanged', (event) => {
1465
+ this.handleAccountChanged(event.data);
1466
+ });
1467
+ this.communicator.on('networkChanged', (event) => {
1468
+ this.handleNetworkChanged(event.data);
1469
+ });
1470
+ this.communicator.on('balanceChanged', (event) => {
1471
+ this.handleBalanceChanged(event.data);
1472
+ });
1473
+ this.communicator.on('extensionLocked', () => {
1474
+ this.handleExtensionLocked();
1475
+ });
1476
+ this.communicator.on('extensionUnlocked', () => {
1477
+ this.handleExtensionUnlocked();
1478
+ });
1479
+ this.communicator.on('transactionConfirmed', (event) => {
1480
+ this.handleTransactionConfirmed(event.data);
1481
+ });
1482
+ this.logger.log('Extension event listeners setup complete');
1483
+ }
1484
+ /**
1485
+ * Handle account changed event from extension
1486
+ */
1487
+ handleAccountChanged(data) {
1488
+ const previousAddress = this.connectionInfo.address;
1489
+ this.connectionInfo.address = data.address;
1490
+ if (data.balance) {
1491
+ this.connectionInfo.balance = data.balance;
1492
+ }
1493
+ const accountChangedEvent = {
1494
+ previousAddress,
1495
+ newAddress: data.address,
1496
+ balance: data.balance
1497
+ };
1498
+ this.emit('accountChanged', accountChangedEvent);
1499
+ this.logger.log('Account changed:', accountChangedEvent);
1500
+ }
1501
+ /**
1502
+ * Handle network changed event from extension
1503
+ */
1504
+ handleNetworkChanged(data) {
1505
+ const previousNetwork = this.connectionInfo.networkInfo;
1506
+ this.connectionInfo.networkInfo = data.networkInfo;
1507
+ const networkChangedEvent = {
1508
+ previousNetwork,
1509
+ newNetwork: data.networkInfo
1510
+ };
1511
+ this.emit('networkChanged', networkChangedEvent);
1512
+ this.logger.log('Network changed:', networkChangedEvent);
1513
+ }
1514
+ /**
1515
+ * Handle balance changed event from extension
1516
+ */
1517
+ handleBalanceChanged(data) {
1518
+ const previousBalance = this.connectionInfo.balance;
1519
+ this.connectionInfo.balance = data.balance;
1520
+ const balanceChangedEvent = {
1521
+ address: this.connectionInfo.address,
1522
+ previousBalance,
1523
+ newBalance: data.balance
1524
+ };
1525
+ this.emit('balanceChanged', balanceChangedEvent);
1526
+ this.logger.log('Balance changed:', balanceChangedEvent);
1527
+ }
1528
+ /**
1529
+ * Handle extension locked event
1530
+ */
1531
+ handleExtensionLocked() {
1532
+ this.connectionInfo = { isConnected: false };
1533
+ const disconnectEvent = {
1534
+ reason: 'extension_locked'
1535
+ };
1536
+ this.emit('disconnect', disconnectEvent);
1537
+ this.logger.log('Extension locked - disconnected');
1538
+ }
1539
+ /**
1540
+ * Handle extension unlocked event
1541
+ */
1542
+ handleExtensionUnlocked() {
1543
+ // Attempt to restore connection
1544
+ this.getConnectionStatus().catch(() => {
1545
+ this.logger.warn('Could not restore connection after unlock');
1546
+ });
1547
+ this.logger.log('Extension unlocked');
1548
+ }
1549
+ /**
1550
+ * Handle transaction confirmed event
1551
+ */
1552
+ handleTransactionConfirmed(data) {
1553
+ this.emit('transactionConfirmed', {
1554
+ txHash: data.txHash,
1555
+ transaction: data.transaction,
1556
+ confirmations: data.confirmations
1557
+ });
1558
+ // Refresh balance after transaction confirmation
1559
+ setTimeout(() => {
1560
+ this.getBalance(true).catch(() => { });
1561
+ }, 2000);
1562
+ this.logger.log('Transaction confirmed:', data.txHash);
1563
+ }
1564
+ // ===================
1565
+ // CLEANUP
1566
+ // ===================
1567
+ /**
1568
+ * Clean up SDK resources
1569
+ */
1570
+ cleanup() {
1571
+ this.communicator.cleanup();
1572
+ this.removeAllListeners();
1573
+ this.connectionInfo = { isConnected: false };
1574
+ this.isInitialized = false;
1575
+ this.logger.log('SDK cleanup complete');
1576
+ }
1577
+ }
1578
+
1579
+ var wallet = /*#__PURE__*/Object.freeze({
1580
+ __proto__: null,
1581
+ ZeroXIOWallet: ZeroXIOWallet
1582
+ });
1583
+
1584
+ /**
1585
+ * 0xio Wallet SDK - Main Entry Point
1586
+ * Official SDK for integrating with 0xio Wallet Extension
1587
+ *
1588
+ * @example
1589
+ * ```typescript
1590
+ * import { ZeroXIOWallet } from '@0xgery/wallet-sdk';
1591
+ *
1592
+ * const wallet = new ZeroXIOWallet({
1593
+ * appName: 'My DApp',
1594
+ * appDescription: 'An awesome decentralized application',
1595
+ * requiredPermissions: ['read_balance', 'send_transactions']
1596
+ * });
1597
+ *
1598
+ * await wallet.initialize();
1599
+ * await wallet.connect();
1600
+ *
1601
+ * const balance = await wallet.getBalance();
1602
+ * console.log('Balance:', balance.total, 'OCT');
1603
+ * ```
1604
+ */
1605
+ // Main exports
1606
+ // Version information
1607
+ const SDK_VERSION = '2.1.1';
1608
+ const SUPPORTED_EXTENSION_VERSIONS = ['2.0.1', '2.0.3', '2.0.4'];
1609
+ // Quick setup function for simple use cases
1610
+ async function createZeroXIOWallet(config) {
1611
+ const { ZeroXIOWallet } = await Promise.resolve().then(function () { return wallet; });
1612
+ const wallet$1 = new ZeroXIOWallet({
1613
+ appName: config.appName,
1614
+ appDescription: config.appDescription,
1615
+ debug: config.debug || false
1616
+ });
1617
+ await wallet$1.initialize();
1618
+ if (config.autoConnect) {
1619
+ try {
1620
+ await wallet$1.connect();
1621
+ }
1622
+ catch (error) {
1623
+ if (config.debug) ;
1624
+ // Don't throw - let the app handle connection manually
1625
+ }
1626
+ }
1627
+ return wallet$1;
1628
+ }
1629
+ // Legacy alias for backward compatibility
1630
+ const createOctraWallet = createZeroXIOWallet;
1631
+ // Browser detection and compatibility check
1632
+ function checkSDKCompatibility() {
1633
+ const issues = [];
1634
+ const recommendations = [];
1635
+ // Basic browser support check
1636
+ if (typeof window === 'undefined') {
1637
+ issues.push('Window object not available');
1638
+ recommendations.push('SDK must be used in a browser environment');
1639
+ }
1640
+ // Check for extension APIs
1641
+ if (typeof window !== 'undefined') {
1642
+ const win = window;
1643
+ if (!win.chrome || !win.chrome.runtime) {
1644
+ issues.push('Chrome extension APIs not available');
1645
+ recommendations.push('This SDK requires a Chromium-based browser (Chrome, Edge, Brave, etc.)');
1646
+ }
1647
+ }
1648
+ return {
1649
+ compatible: issues.length === 0,
1650
+ issues,
1651
+ recommendations
1652
+ };
1653
+ }
1654
+ // Development helpers
1655
+ if (typeof window !== 'undefined') {
1656
+ // Expose SDK version for debugging (support both old and new naming)
1657
+ window.__OCTRA_SDK_VERSION__ = SDK_VERSION;
1658
+ window.__ZEROXIO_SDK_VERSION__ = SDK_VERSION;
1659
+ // Development mode detection
1660
+ const isDevelopment = window.location.hostname === 'localhost' ||
1661
+ window.location.hostname === '127.0.0.1' ||
1662
+ window.location.hostname.includes('dev');
1663
+ if (isDevelopment) {
1664
+ // Set debug flag but don't automatically log
1665
+ window.__OCTRA_SDK_DEBUG__ = true;
1666
+ window.__ZEROXIO_SDK_DEBUG__ = true;
1667
+ // Expose debugging utilities (new branding)
1668
+ window.__ZEROXIO_SDK_UTILS__ = {
1669
+ enableDebugMode: () => {
1670
+ window.__ZEROXIO_SDK_DEBUG__ = true;
1671
+ window.__OCTRA_SDK_DEBUG__ = true;
1672
+ console.log('[0xio SDK] Debug mode enabled');
1673
+ },
1674
+ disableDebugMode: () => {
1675
+ window.__ZEROXIO_SDK_DEBUG__ = false;
1676
+ window.__OCTRA_SDK_DEBUG__ = false;
1677
+ console.log('[0xio SDK] Debug mode disabled');
1678
+ },
1679
+ getSDKInfo: () => ({
1680
+ version: SDK_VERSION,
1681
+ supportedExtensions: SUPPORTED_EXTENSION_VERSIONS,
1682
+ debugMode: !!window.__ZEROXIO_SDK_DEBUG__,
1683
+ environment: isDevelopment ? 'development' : 'production'
1684
+ }),
1685
+ simulateExtensionEvent: (eventType, data) => {
1686
+ window.postMessage({
1687
+ source: '0xio-sdk-bridge',
1688
+ event: { type: eventType, data }
1689
+ }, '*');
1690
+ console.log('[0xio SDK] Simulated extension event:', eventType, data);
1691
+ },
1692
+ showWelcome: () => {
1693
+ console.log(`[0xio SDK] Development mode - SDK v${SDK_VERSION}`);
1694
+ console.log('[0xio SDK] Debug utilities available at window.__ZEROXIO_SDK_UTILS__');
1695
+ }
1696
+ };
1697
+ // Legacy alias for backward compatibility
1698
+ window.__OCTRA_SDK_UTILS__ = window.__ZEROXIO_SDK_UTILS__;
1699
+ // Only show welcome message if specifically requested or on localhost
1700
+ // if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
1701
+ // (window as any).__ZEROXIO_SDK_UTILS__.showWelcome();
1702
+ // }
1703
+ }
1704
+ }
1705
+
1706
+ exports.DEFAULT_NETWORK_ID = DEFAULT_NETWORK_ID;
1707
+ exports.EventEmitter = EventEmitter;
1708
+ exports.ExtensionCommunicator = ExtensionCommunicator;
1709
+ exports.NETWORKS = NETWORKS;
1710
+ exports.SDK_CONFIG = SDK_CONFIG;
1711
+ exports.SDK_VERSION = SDK_VERSION;
1712
+ exports.SUPPORTED_EXTENSION_VERSIONS = SUPPORTED_EXTENSION_VERSIONS;
1713
+ exports.ZeroXIOWallet = ZeroXIOWallet;
1714
+ exports.ZeroXIOWalletError = ZeroXIOWalletError;
1715
+ exports.checkBrowserSupport = checkBrowserSupport;
1716
+ exports.checkSDKCompatibility = checkSDKCompatibility;
1717
+ exports.createDefaultBalance = createDefaultBalance;
1718
+ exports.createErrorMessage = createErrorMessage;
1719
+ exports.createLogger = createLogger;
1720
+ exports.createOctraWallet = createOctraWallet;
1721
+ exports.createZeroXIOWallet = createZeroXIOWallet;
1722
+ exports.delay = delay;
1723
+ exports.formatAddress = formatAddress;
1724
+ exports.formatOCT = formatOCT;
1725
+ exports.formatTimestamp = formatTimestamp;
1726
+ exports.formatTxHash = formatTxHash;
1727
+ exports.formatZeroXIO = formatOCT;
1728
+ exports.fromMicroOCT = fromMicroOCT;
1729
+ exports.fromMicroZeroXIO = fromMicroOCT;
1730
+ exports.generateMockData = generateMockData;
1731
+ exports.getAllNetworks = getAllNetworks;
1732
+ exports.getDefaultNetwork = getDefaultNetwork;
1733
+ exports.getNetworkConfig = getNetworkConfig;
1734
+ exports.isBrowser = isBrowser;
1735
+ exports.isErrorType = isErrorType;
1736
+ exports.isValidAddress = isValidAddress;
1737
+ exports.isValidAmount = isValidAmount;
1738
+ exports.isValidFeeLevel = isValidFeeLevel;
1739
+ exports.isValidMessage = isValidMessage;
1740
+ exports.isValidNetworkId = isValidNetworkId;
1741
+ exports.retry = retry;
1742
+ exports.toMicroOCT = toMicroOCT;
1743
+ exports.toMicroZeroXIO = toMicroOCT;
1744
+ exports.withTimeout = withTimeout;
1745
+ //# sourceMappingURL=index.js.map