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