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