@0xio/sdk 2.3.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,60 @@
2
2
 
3
3
  All notable changes to the 0xio Wallet SDK will be documented in this file.
4
4
 
5
+ ## [2.4.1] - 2026-04-14
6
+
7
+ ### Security
8
+ - **[CRITICAL] postMessage origin validation**: Parent-frame messages now validated against a strict trusted origins set instead of accepting all origins. Prevents malicious pages from intercepting wallet requests via iframe embedding.
9
+ - **[CRITICAL] Removed auto-trust for iframes**: SDK no longer assumes any iframe parent is a wallet bridge. Must receive a `walletReady` signal from a trusted origin first.
10
+ - **[CRITICAL] Response binding**: Only responses with matching pending request IDs are processed. Forged responses from injected scripts are rejected.
11
+ - **[HIGH] Removed `simulateExtensionEvent`**: Dev utility that could be exploited on staging builds to inject fake wallet events has been removed.
12
+ - **[HIGH] No wildcard postMessage**: `postMessageToExtension` now uses specific trusted origins (`tauri://localhost`, etc.) instead of `'*'` for parent-frame communication.
13
+
14
+ ### Fixed
15
+ - **No retry on user rejection**: `retry()` detects rejection/denied/cancelled errors and throws immediately. Prevents double confirmation popups.
16
+ - **`withTimeout` timer leak**: Timer is now cleared via `.finally()` when the promise resolves, preventing 30s memory retention per request.
17
+ - **`retry` off-by-one**: `maxRetries=1` now correctly means 1 initial + 1 retry = 2 total (was 3).
18
+ - **Message listener cleanup**: `cleanup()` now removes the `window.addEventListener('message')` listener, preventing accumulation on re-instantiation.
19
+ - **Type compatibility**: Replaced `NodeJS.Timeout` with `ReturnType<typeof setTimeout>` for browser-only environments.
20
+ - **`process.env` guard**: `createLogger` now uses optional chaining for `process.env.NODE_ENV`, preventing ReferenceError when imported in browser without bundler.
21
+ - **Duplicate `isValidNetworkId`**: Removed duplicate export from `utils.ts`, canonical version in `config/networks.ts`.
22
+
23
+ ### Added
24
+ - **`setTrustedOrigins(origins)`**: New method to configure allowed parent-frame origins for iframe/bridge communication.
25
+
26
+ ### Documentation
27
+ - Fixed all event listener examples to use `event.data.xxx` (WalletEvent wrapper)
28
+ - Fixed `TransactionHistory` type (`totalCount`/`hasMore` instead of `total`/`limit`/`totalPages`)
29
+ - Fixed `retry()` docs (positional args, not options object)
30
+ - Fixed `formatZeroXIO` return value (no " OCT" suffix)
31
+ - Fixed `toMicroZeroXIO` return type (string, not number)
32
+ - Removed nonexistent `ConnectOptions.timeout`, `.requestPrivateAccess`
33
+ - Removed nonexistent `ConnectionInfo.permissions`
34
+ - Changed `ErrorCode.TIMEOUT` to `ErrorCode.NETWORK_ERROR`
35
+ - Updated mainnet privacy support to "Yes"
36
+
37
+ ## [2.4.0] - 2026-03-24
38
+
39
+ ### Added
40
+ - **Desktop/Mobile DApp Bridge**: SDK now supports running inside iframes (desktop browser) and WebViews (mobile browser). Requests are relayed to the parent frame automatically.
41
+ - **Parent Frame Detection**: `postMessageToExtension()` now posts to both `window` (extension content script) and `window.parent` (iframe bridge) when running inside a frame.
42
+ - **Frame-Aware Message Listener**: `setupMessageListener()` now accepts messages from `window.parent` in addition to same-window, enabling desktop/mobile wallet bridges to communicate with DApps.
43
+ - **walletReady via postMessage**: `startExtensionDetection()` now detects `walletReady` events sent via `postMessage` from parent frames, in addition to DOM events and global signals.
44
+ - **Auto Frame Detection**: When `window.parent !== window`, the SDK assumes a wallet bridge is available and marks the wallet as detected.
45
+
46
+ ### Security
47
+ - Parent frame messages are only accepted from `window.parent`, not arbitrary origins
48
+ - Extension content script messages continue to use strict origin validation
49
+
50
+ ### Compatibility
51
+ - Fully backward compatible — extension-based DApps work unchanged
52
+ - Desktop (0xio Desktop): DApps loaded in BrowserScreen iframe now auto-connect
53
+ - Mobile (0xio App): DApps loaded in WebView browser now auto-connect via existing bridge
54
+ - Mainnet Alpha: Extension v2.0.1+
55
+ - Devnet: Extension v2.2.1+
56
+
57
+ ---
58
+
5
59
  ## [2.3.0] - 2026-03-10
6
60
 
7
61
  ### Added
@@ -47,7 +101,7 @@ All notable changes to the 0xio Wallet SDK will be documented in this file.
47
101
 
48
102
  | Network | RPC | Explorer | Privacy | Testnet |
49
103
  |---------|-----|----------|---------|---------|
50
- | Mainnet Alpha | `https://octra.network` | `https://octrascan.io` | No | No |
104
+ | Mainnet | `http://46.101.86.250:8080` | `https://lite.octrascan.io` | Yes | No |
51
105
  | Devnet | `http://165.227.225.79:8080` | `https://devnet.octrascan.io` | Yes | Yes |
52
106
  | Custom | User-defined | User-defined | No | No |
53
107
 
package/README.md CHANGED
@@ -1,18 +1,21 @@
1
1
  # 0xio Wallet SDK
2
2
 
3
- **Version:** 2.3.0
3
+ **Version:** 2.4.1
4
4
 
5
5
  Official TypeScript SDK for integrating DApps with 0xio Wallet on Octra Network.
6
6
 
7
- ## What's New in v2.3.0
7
+ ## What's New in v2.4.1
8
8
 
9
- - **Smart Contract Calls**: New `callContract()` for state-changing contract interaction (signed, with approval popup)
10
- - **Contract View Calls**: New `contractCallView()` for read-only queries (no signing, no popup)
11
- - **Contract Storage**: New `getContractStorage()` to read on-chain contract storage by key
12
- - **Type Safety**: New `ContractParams` type (`ReadonlyArray<string | number | boolean>`) replaces `any` in contract interfaces; typed event handlers
13
- - **Error Consistency**: `getNetworkConfig()` now throws `ZeroXIOWalletError` instead of generic `Error`
14
- - **Security Fixes**: Fixed wildcard origin in dev utils, added signMessage length limit, narrowed dev detection
15
- - **Message Limit**: Raised `isValidMessage()` from 280 to 100K chars to support contract call parameters
9
+ - **No retry on user rejection**: Transactions, contract calls, and sign requests rejected by the user no longer trigger automatic retry. Prevents double confirmation popups.
10
+ - **Type fixes**: Replaced `NodeJS.Timeout` with `ReturnType<typeof setTimeout>` for browser compatibility.
11
+
12
+ ## v2.4.0
13
+
14
+ - **Desktop/Mobile DApp Bridge**: SDK now supports running inside iframes (0xio Desktop) and WebViews (0xio App). Requests are relayed to the parent frame automatically.
15
+ - **Auto Frame Detection**: When `window.parent !== window`, the SDK assumes a wallet bridge is available and marks the wallet as detected.
16
+ - **Frame-Aware Messaging**: `postMessageToExtension()` posts to both `window` and `window.parent` when running inside a frame; `setupMessageListener()` accepts messages from `window.parent`.
17
+ - **walletReady via postMessage**: Extension detection now recognizes `walletReady` events from parent frames.
18
+ - **Fully backward compatible** — extension-based DApps work unchanged with no code changes needed.
16
19
 
17
20
  ## Installation
18
21
 
@@ -165,11 +168,11 @@ console.log('Signature:', signature);
165
168
  ### Events
166
169
 
167
170
  ```typescript
168
- wallet.on('connect', (event) => console.log('Connected:', event.address));
171
+ wallet.on('connect', (event) => console.log('Connected:', event.data.address));
169
172
  wallet.on('disconnect', (event) => console.log('Disconnected'));
170
- wallet.on('balanceChanged', (event) => console.log('New balance:', event.newBalance.total));
171
- wallet.on('accountChanged', (event) => console.log('Account changed:', event.newAddress));
172
- wallet.on('networkChanged', (event) => console.log('Network:', event.newNetwork.name));
173
+ wallet.on('balanceChanged', (event) => console.log('New balance:', event.data.newBalance.total));
174
+ wallet.on('accountChanged', (event) => console.log('Account changed:', event.data.newAddress));
175
+ wallet.on('networkChanged', (event) => console.log('Network:', event.data.newNetwork.name));
173
176
  ```
174
177
 
175
178
  ## Error Handling
@@ -223,13 +226,13 @@ console.log(devnet.isTestnet); // true
223
226
 
224
227
  // Get mainnet config
225
228
  const mainnet = getNetworkConfig('mainnet');
226
- console.log(mainnet.rpcUrl); // https://octra.network
227
- console.log(mainnet.supportsPrivacy); // false
229
+ console.log(mainnet.rpcUrl); // http://46.101.86.250:8080
230
+ console.log(mainnet.supportsPrivacy); // true
228
231
  ```
229
232
 
230
233
  | Network | Privacy (FHE) | Explorer |
231
234
  |---------|:---:|---|
232
- | Mainnet Alpha | No | [octrascan.io](https://octrascan.io) |
235
+ | Mainnet Alpha | Yes | [octrascan.io](https://octrascan.io) |
233
236
  | Devnet | Yes | [devnet.octrascan.io](https://devnet.octrascan.io) |
234
237
 
235
238
  ### NetworkInfo Type
@@ -248,10 +251,22 @@ interface NetworkInfo {
248
251
  }
249
252
  ```
250
253
 
254
+ ## Desktop & Mobile Support
255
+
256
+ The SDK automatically detects when your DApp is running inside:
257
+
258
+ - **0xio Desktop's built-in browser** — Your DApp is loaded in an iframe. The SDK detects `window.parent !== window` and relays all requests to the desktop wallet via the iframe bridge.
259
+ - **0xio App's built-in browser** — Your DApp is loaded in a WebView. The SDK communicates with the mobile wallet via the existing WebView bridge.
260
+ - **0xio Wallet Extension** — Standard browser extension communication (unchanged).
261
+
262
+ No code changes are needed for DApp developers. Just use the SDK as normal and it will auto-detect the environment and choose the correct transport.
263
+
251
264
  ## Requirements
252
265
 
253
266
  - 0xio Wallet Extension v2.0.1 or higher (Mainnet Alpha)
254
267
  - 0xio Wallet Extension v2.2.1 or higher (Devnet — required for contract calls and privacy features)
268
+ - 0xio Desktop v1.0+ (for iframe bridge support)
269
+ - 0xio App v1.0+ (for WebView bridge support)
255
270
  - Modern browser (Chrome, Firefox, Edge, Brave)
256
271
 
257
272
  ## Documentation
package/dist/index.d.ts CHANGED
@@ -412,7 +412,7 @@ declare class ZeroXIOWallet extends EventEmitter {
412
412
  * to ensure secure wallet interactions.
413
413
  *
414
414
  * @module communication
415
- * @version 2.2.0
415
+ * @version 2.4.1
416
416
  * @license MIT
417
417
  */
418
418
 
@@ -452,6 +452,10 @@ declare class ExtensionCommunicator extends EventEmitter {
452
452
  private extensionDetectionInterval;
453
453
  /** Current extension availability state */
454
454
  private isExtensionAvailableState;
455
+ /** Message listener reference for cleanup */
456
+ private messageListener;
457
+ /** Trusted parent origins for iframe communication */
458
+ private trustedOrigins;
455
459
  /** Maximum number of concurrent pending requests */
456
460
  private readonly MAX_CONCURRENT_REQUESTS;
457
461
  /** Time window for rate limiting (milliseconds) */
@@ -465,7 +469,12 @@ declare class ExtensionCommunicator extends EventEmitter {
465
469
  *
466
470
  * @param {boolean} debug - Enable debug logging
467
471
  */
468
- constructor(debug?: boolean);
472
+ constructor(debug?: boolean, trustedOrigins?: string[]);
473
+ /**
474
+ * Add trusted origins for iframe/bridge communication
475
+ * Call this before connecting if your dApp runs inside a trusted frame
476
+ */
477
+ setTrustedOrigins(origins: string[]): void;
469
478
  /**
470
479
  * Initialize communication with the wallet extension
471
480
  *
@@ -614,6 +623,10 @@ declare function getNetworkConfig(networkId?: string): NetworkInfo;
614
623
  * Get all available networks
615
624
  */
616
625
  declare function getAllNetworks(): NetworkInfo[];
626
+ /**
627
+ * Check if network ID is valid
628
+ */
629
+ declare function isValidNetworkId(networkId: string): boolean;
617
630
 
618
631
  /**
619
632
  * Default balance structure
@@ -623,7 +636,7 @@ declare function createDefaultBalance(total?: number): Balance;
623
636
  * SDK Configuration constants
624
637
  */
625
638
  declare const SDK_CONFIG: {
626
- readonly version: "2.3.0";
639
+ readonly version: "2.4.1";
627
640
  readonly defaultNetworkId: "mainnet";
628
641
  readonly communicationTimeout: 30000;
629
642
  readonly retryAttempts: 3;
@@ -647,10 +660,6 @@ declare function isValidAddress(address: string): boolean;
647
660
  * Validate transaction amount
648
661
  */
649
662
  declare function isValidAmount(amount: number): boolean;
650
- /**
651
- * Validate network ID
652
- */
653
- declare function isValidNetworkId(networkId: string): boolean;
654
663
  /**
655
664
  * Validate transaction message
656
665
  */
@@ -750,7 +759,7 @@ declare function createLogger(prefix: string, debug: boolean): {
750
759
  groupEnd: () => void;
751
760
  };
752
761
 
753
- declare const SDK_VERSION = "2.3.0";
762
+ declare const SDK_VERSION = "2.4.1";
754
763
  declare const MIN_EXTENSION_VERSION = "2.0.1";
755
764
  declare const MIN_EXTENSION_VERSION_DEVNET = "2.2.1";
756
765
  declare const SUPPORTED_EXTENSION_VERSIONS = "^2.0.1";
package/dist/index.esm.js CHANGED
@@ -164,16 +164,6 @@ function isValidAmount(amount) {
164
164
  Number.isFinite(amount) &&
165
165
  amount <= Number.MAX_SAFE_INTEGER;
166
166
  }
167
- /**
168
- * Validate network ID
169
- */
170
- function isValidNetworkId(networkId) {
171
- if (!networkId || typeof networkId !== 'string') {
172
- return false;
173
- }
174
- const validNetworks = ['mainnet', 'devnet', 'custom'];
175
- return validNetworks.includes(networkId.toLowerCase());
176
- }
177
167
  /**
178
168
  * Validate transaction message
179
169
  */
@@ -317,14 +307,21 @@ function delay(ms) {
317
307
  */
318
308
  async function retry(operation, maxRetries = 3, baseDelay = 1000) {
319
309
  let lastError;
320
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
310
+ // maxRetries = number of retries AFTER the first attempt
311
+ // Total attempts = 1 (initial) + maxRetries
312
+ for (let attempt = 0; attempt < 1 + maxRetries; attempt++) {
321
313
  try {
322
314
  return await operation();
323
315
  }
324
316
  catch (error) {
325
317
  lastError = error;
326
- if (attempt === maxRetries) {
327
- break; // Last attempt failed
318
+ // Never retry user rejections — these are intentional
319
+ const msg = lastError.message?.toLowerCase() || '';
320
+ if (msg.includes('rejected') || msg.includes('denied') || msg.includes('cancelled') || msg.includes('user refused')) {
321
+ throw lastError;
322
+ }
323
+ if (attempt >= maxRetries) {
324
+ break; // All retries exhausted
328
325
  }
329
326
  // Exponential backoff: 1s, 2s, 4s, etc.
330
327
  const delayMs = baseDelay * Math.pow(2, attempt);
@@ -337,12 +334,15 @@ async function retry(operation, maxRetries = 3, baseDelay = 1000) {
337
334
  * Timeout wrapper for promises
338
335
  */
339
336
  function withTimeout(promise, timeoutMs, timeoutMessage = 'Operation timed out') {
337
+ let timer;
340
338
  const timeoutPromise = new Promise((_, reject) => {
341
- setTimeout(() => {
339
+ timer = setTimeout(() => {
342
340
  reject(new ZeroXIOWalletError(ErrorCode.NETWORK_ERROR, timeoutMessage));
343
341
  }, timeoutMs);
344
342
  });
345
- return Promise.race([promise, timeoutPromise]);
343
+ return Promise.race([promise, timeoutPromise]).finally(() => {
344
+ clearTimeout(timer);
345
+ });
346
346
  }
347
347
  // ===================
348
348
  // BROWSER UTILITIES
@@ -394,12 +394,12 @@ function generateMockData() {
394
394
  },
395
395
  networkInfo: {
396
396
  id: 'mainnet',
397
- name: 'Octra Mainnet Alpha',
398
- rpcUrl: 'https://octra.network',
399
- explorerUrl: 'https://octrascan.io/transactions/',
400
- explorerAddressUrl: 'https://octrascan.io/addresses/',
401
- indexerUrl: 'https://network.octrascan.com',
402
- supportsPrivacy: false,
397
+ name: 'Octra Mainnet',
398
+ rpcUrl: 'http://46.101.86.250:8080',
399
+ explorerUrl: 'https://lite.octrascan.io/tx.html?hash=',
400
+ explorerAddressUrl: 'https://lite.octrascan.io/address.html?addr=',
401
+ indexerUrl: 'https://lite.octrascan.io',
402
+ supportsPrivacy: true,
403
403
  color: '#f59e0b',
404
404
  isTestnet: false
405
405
  }
@@ -409,7 +409,8 @@ function generateMockData() {
409
409
  * Create development logger
410
410
  */
411
411
  function createLogger(prefix, debug) {
412
- const isDevelopment = typeof window !== 'undefined' && (window.location.hostname === 'localhost' ||
412
+ const isDevelopment = typeof window !== 'undefined' && ((typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') ||
413
+ window.location.hostname === 'localhost' ||
413
414
  window.location.hostname === '127.0.0.1' ||
414
415
  window.__OCTRA_SDK_DEBUG__);
415
416
  // Only enable logging in development mode AND when debug is explicitly enabled
@@ -467,7 +468,7 @@ function createLogger(prefix, debug) {
467
468
  * to ensure secure wallet interactions.
468
469
  *
469
470
  * @module communication
470
- * @version 2.2.0
471
+ * @version 2.4.1
471
472
  * @license MIT
472
473
  */
473
474
  /**
@@ -499,7 +500,7 @@ class ExtensionCommunicator extends EventEmitter {
499
500
  *
500
501
  * @param {boolean} debug - Enable debug logging
501
502
  */
502
- constructor(debug = false) {
503
+ constructor(debug = false, trustedOrigins = []) {
503
504
  super(debug);
504
505
  /** Legacy request counter (deprecated, kept for fallback) */
505
506
  this.requestId = 0;
@@ -511,6 +512,10 @@ class ExtensionCommunicator extends EventEmitter {
511
512
  this.extensionDetectionInterval = null;
512
513
  /** Current extension availability state */
513
514
  this.isExtensionAvailableState = false;
515
+ /** Message listener reference for cleanup */
516
+ this.messageListener = null;
517
+ /** Trusted parent origins for iframe communication */
518
+ this.trustedOrigins = [];
514
519
  // Rate limiting configuration
515
520
  /** Maximum number of concurrent pending requests */
516
521
  this.MAX_CONCURRENT_REQUESTS = 50;
@@ -521,9 +526,17 @@ class ExtensionCommunicator extends EventEmitter {
521
526
  /** Timestamps of recent requests for rate limiting */
522
527
  this.requestTimestamps = [];
523
528
  this.logger = createLogger('ExtensionCommunicator', debug);
529
+ this.trustedOrigins = trustedOrigins;
524
530
  this.setupMessageListener();
525
531
  this.startExtensionDetection();
526
532
  }
533
+ /**
534
+ * Add trusted origins for iframe/bridge communication
535
+ * Call this before connecting if your dApp runs inside a trusted frame
536
+ */
537
+ setTrustedOrigins(origins) {
538
+ this.trustedOrigins = origins;
539
+ }
527
540
  /**
528
541
  * Initialize communication with the wallet extension
529
542
  *
@@ -649,33 +662,51 @@ class ExtensionCommunicator extends EventEmitter {
649
662
  return; // Not in browser environment
650
663
  }
651
664
  const allowedOrigin = window.location.origin;
652
- window.addEventListener('message', (event) => {
653
- // SECURITY: Validate origin first - critical security check
654
- if (event.origin !== allowedOrigin) {
655
- this.logger.warn('Blocked message from untrusted origin:', event.origin);
665
+ // Store allowed origins for parent frame communication
666
+ // Desktop (Tauri): tauri://localhost or https://tauri.localhost
667
+ // Mobile (Expo): about:blank or custom scheme
668
+ const trustedParentOrigins = new Set([
669
+ allowedOrigin,
670
+ 'tauri://localhost',
671
+ 'https://tauri.localhost',
672
+ 'http://localhost',
673
+ 'https://localhost',
674
+ ]);
675
+ // Allow dApps to register additional trusted origins
676
+ if (this.trustedOrigins) {
677
+ for (const origin of this.trustedOrigins) {
678
+ trustedParentOrigins.add(origin);
679
+ }
680
+ }
681
+ this.messageListener = (event) => {
682
+ // Strict origin validation
683
+ const isFromSameOrigin = event.origin === allowedOrigin;
684
+ const isFromTrustedParent = event.source === window.parent
685
+ && window.parent !== window
686
+ && trustedParentOrigins.has(event.origin);
687
+ if (!isFromSameOrigin && !isFromTrustedParent) {
656
688
  return;
657
689
  }
658
- // Only accept messages from same window
659
- if (event.source !== window) {
690
+ // Only accept from same window (extension content script) or trusted parent frame
691
+ if (event.source !== window && event.source !== window.parent) {
660
692
  return;
661
693
  }
662
- // Check if it's a 0xio SDK response
694
+ // Verify message structure
663
695
  if (!event.data || event.data.source !== '0xio-sdk-bridge') {
664
696
  return;
665
697
  }
666
- // Handle different types of messages
698
+ // Validate response has a pending request (prevents forged responses)
667
699
  if (event.data.response) {
668
- // Regular request/response
669
700
  const response = event.data.response;
670
- if (response && response.id) {
701
+ if (response && response.id && this.pendingRequests.has(response.id)) {
671
702
  this.handleExtensionResponse(response);
672
703
  }
673
704
  }
674
705
  else if (event.data.event) {
675
- // Event notification from extension
676
706
  this.handleExtensionEvent(event.data.event);
677
707
  }
678
- });
708
+ };
709
+ window.addEventListener('message', this.messageListener);
679
710
  this.logger.log('Message listener setup complete');
680
711
  }
681
712
  /**
@@ -728,12 +759,32 @@ class ExtensionCommunicator extends EventEmitter {
728
759
  * Post message to extension via content script
729
760
  */
730
761
  postMessageToExtension(request) {
731
- // SECURITY: Use specific origin instead of wildcard
732
- const targetOrigin = window.location.origin;
733
- window.postMessage({
734
- source: '0xio-sdk-request',
735
- request
736
- }, targetOrigin);
762
+ const msg = { source: '0xio-sdk-request', request };
763
+ // Post to same window (extension content script picks it up)
764
+ window.postMessage(msg, window.location.origin);
765
+ // Also post to parent frame if in an iframe (desktop/mobile bridge)
766
+ // Use specific origin instead of wildcard to prevent interception
767
+ if (window.parent !== window) {
768
+ try {
769
+ // Try same-origin first (works for Tauri, same-domain iframes)
770
+ window.parent.postMessage(msg, window.location.origin);
771
+ }
772
+ catch {
773
+ // Cross-origin parent (e.g. tauri://localhost) — use known trusted origins only
774
+ const trustedOrigins = ['tauri://localhost', 'https://tauri.localhost'];
775
+ if (this.trustedOrigins) {
776
+ trustedOrigins.push(...this.trustedOrigins);
777
+ }
778
+ for (const origin of trustedOrigins) {
779
+ try {
780
+ window.parent.postMessage(msg, origin);
781
+ }
782
+ catch {
783
+ // Origin not matching, skip
784
+ }
785
+ }
786
+ }
787
+ }
737
788
  }
738
789
  /**
739
790
  * Check if we're in a context that can communicate with extension
@@ -828,6 +879,27 @@ class ExtensionCommunicator extends EventEmitter {
828
879
  this.logger.log('Received octraWalletReady event');
829
880
  this.isExtensionAvailableState = true;
830
881
  });
882
+ // Listen for desktop/mobile bridge walletReady via postMessage (with origin validation)
883
+ window.addEventListener('message', (event) => {
884
+ if (event.data?.source === '0xio-sdk-bridge' && event.data?.event?.type === 'walletReady') {
885
+ const isSameOrigin = event.origin === window.location.origin;
886
+ const isTrusted = this.trustedOrigins.includes(event.origin)
887
+ || event.origin === 'tauri://localhost'
888
+ || event.origin === 'https://tauri.localhost';
889
+ if (isSameOrigin || isTrusted) {
890
+ this.logger.log('Received walletReady via postMessage from trusted origin');
891
+ this.isExtensionAvailableState = true;
892
+ }
893
+ else {
894
+ this.logger.warn(`Ignored walletReady from untrusted origin: ${event.origin}`);
895
+ }
896
+ }
897
+ });
898
+ // If running inside a frame, do NOT auto-trust the parent.
899
+ // Wait for a walletReady message from a trusted origin instead.
900
+ if (window.parent !== window) {
901
+ this.logger.log('Running inside a frame — waiting for trusted walletReady signal');
902
+ }
831
903
  // Initial check (in case extension was already injected)
832
904
  this.checkExtensionAvailability();
833
905
  // Set up periodic checks as fallback
@@ -963,6 +1035,11 @@ class ExtensionCommunicator extends EventEmitter {
963
1035
  this.pendingRequests.clear();
964
1036
  this.isInitialized = false;
965
1037
  this.isExtensionAvailableState = false;
1038
+ // Remove message listener to prevent accumulation
1039
+ if (this.messageListener) {
1040
+ window.removeEventListener('message', this.messageListener);
1041
+ this.messageListener = null;
1042
+ }
966
1043
  // Call parent cleanup
967
1044
  this.removeAllListeners();
968
1045
  this.logger.log('Communication cleanup complete');
@@ -987,12 +1064,12 @@ class ExtensionCommunicator extends EventEmitter {
987
1064
  const NETWORKS = {
988
1065
  'mainnet': {
989
1066
  id: 'mainnet',
990
- name: 'Octra Mainnet Alpha',
991
- rpcUrl: 'https://octra.network',
992
- explorerUrl: 'https://octrascan.io/transactions/',
993
- explorerAddressUrl: 'https://octrascan.io/addresses/',
994
- indexerUrl: 'https://network.octrascan.com',
995
- supportsPrivacy: false,
1067
+ name: 'Octra Mainnet',
1068
+ rpcUrl: 'http://46.101.86.250:8080',
1069
+ explorerUrl: 'https://lite.octrascan.io/tx.html?hash=',
1070
+ explorerAddressUrl: 'https://lite.octrascan.io/address.html?addr=',
1071
+ indexerUrl: 'https://lite.octrascan.io',
1072
+ supportsPrivacy: true,
996
1073
  color: '#f59e0b',
997
1074
  isTestnet: false
998
1075
  },
@@ -1036,6 +1113,12 @@ function getNetworkConfig(networkId = DEFAULT_NETWORK_ID) {
1036
1113
  function getAllNetworks() {
1037
1114
  return Object.values(NETWORKS);
1038
1115
  }
1116
+ /**
1117
+ * Check if network ID is valid
1118
+ */
1119
+ function isValidNetworkId(networkId) {
1120
+ return networkId in NETWORKS;
1121
+ }
1039
1122
 
1040
1123
  /**
1041
1124
  * SDK Configuration
@@ -1055,7 +1138,7 @@ function createDefaultBalance(total = 0) {
1055
1138
  * SDK Configuration constants
1056
1139
  */
1057
1140
  const SDK_CONFIG = {
1058
- version: '2.3.0',
1141
+ version: '2.4.1',
1059
1142
  defaultNetworkId: DEFAULT_NETWORK_ID,
1060
1143
  communicationTimeout: 30000, // 30 seconds
1061
1144
  retryAttempts: 3,
@@ -1756,7 +1839,7 @@ var wallet = /*#__PURE__*/Object.freeze({
1756
1839
  */
1757
1840
  // Main exports
1758
1841
  // Version information
1759
- const SDK_VERSION = '2.3.0';
1842
+ const SDK_VERSION = '2.4.1';
1760
1843
  const MIN_EXTENSION_VERSION = '2.0.1'; // Mainnet Alpha
1761
1844
  const MIN_EXTENSION_VERSION_DEVNET = '2.2.1'; // Devnet (contract calls, privacy)
1762
1845
  const SUPPORTED_EXTENSION_VERSIONS = '^2.0.1'; // Supports all versions >= 2.0.1
@@ -1811,7 +1894,8 @@ if (typeof window !== 'undefined') {
1811
1894
  window.__OCTRA_SDK_VERSION__ = SDK_VERSION;
1812
1895
  window.__ZEROXIO_SDK_VERSION__ = SDK_VERSION;
1813
1896
  // Development mode detection
1814
- const isDevelopment = window.location.hostname === 'localhost' ||
1897
+ const isDevelopment = (typeof globalThis !== 'undefined' && globalThis.process?.env?.NODE_ENV === 'development') ||
1898
+ window.location.hostname === 'localhost' ||
1815
1899
  window.location.hostname === '127.0.0.1';
1816
1900
  if (isDevelopment) {
1817
1901
  // Set debug flag but don't automatically log
@@ -1836,13 +1920,7 @@ if (typeof window !== 'undefined') {
1836
1920
  debugMode: !!window.__ZEROXIO_SDK_DEBUG__,
1837
1921
  environment: isDevelopment ? 'development' : 'production'
1838
1922
  }),
1839
- simulateExtensionEvent: (eventType, data) => {
1840
- window.postMessage({
1841
- source: '0xio-sdk-bridge',
1842
- event: { type: eventType, data }
1843
- }, window.location.origin);
1844
- console.log('[0xio SDK] Simulated extension event:', eventType, data);
1845
- },
1923
+ // simulateExtensionEvent removed for security — could be exploited on staging builds
1846
1924
  showWelcome: () => {
1847
1925
  console.log(`[0xio SDK] Development mode - SDK v${SDK_VERSION}`);
1848
1926
  console.log('[0xio SDK] Debug utilities available at window.__ZEROXIO_SDK_UTILS__');