@0xio/sdk 2.4.0 → 2.4.2

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,45 @@
2
2
 
3
3
  All notable changes to the 0xio Wallet SDK will be documented in this file.
4
4
 
5
+ ## [2.4.2] - 2026-04-15
6
+
7
+ ### Fixed
8
+ - **Cross-origin iframe DApp bridge**: SDK now accepts localhost origins as trusted parents for dev/testing. Fixes "Unable to post message" errors when DApps run on different localhost ports inside the desktop browser.
9
+ - **Parent origin capture**: `walletReady` signal now carries `parentOrigin` field. SDK stores it and uses it for cross-origin replies instead of failing silently.
10
+ - **postMessage targeting**: When inside an iframe, SDK replies to the captured parent origin (or wildcard fallback) instead of `window.location.origin` which fails for cross-port scenarios.
11
+
12
+ ## [2.4.1] - 2026-04-14
13
+
14
+ ### Security
15
+ - **[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.
16
+ - **[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.
17
+ - **[CRITICAL] Response binding**: Only responses with matching pending request IDs are processed. Forged responses from injected scripts are rejected.
18
+ - **[HIGH] Removed `simulateExtensionEvent`**: Dev utility that could be exploited on staging builds to inject fake wallet events has been removed.
19
+ - **[HIGH] No wildcard postMessage**: `postMessageToExtension` now uses specific trusted origins (`tauri://localhost`, etc.) instead of `'*'` for parent-frame communication.
20
+
21
+ ### Fixed
22
+ - **No retry on user rejection**: `retry()` detects rejection/denied/cancelled errors and throws immediately. Prevents double confirmation popups.
23
+ - **`withTimeout` timer leak**: Timer is now cleared via `.finally()` when the promise resolves, preventing 30s memory retention per request.
24
+ - **`retry` off-by-one**: `maxRetries=1` now correctly means 1 initial + 1 retry = 2 total (was 3).
25
+ - **Message listener cleanup**: `cleanup()` now removes the `window.addEventListener('message')` listener, preventing accumulation on re-instantiation.
26
+ - **Type compatibility**: Replaced `NodeJS.Timeout` with `ReturnType<typeof setTimeout>` for browser-only environments.
27
+ - **`process.env` guard**: `createLogger` now uses optional chaining for `process.env.NODE_ENV`, preventing ReferenceError when imported in browser without bundler.
28
+ - **Duplicate `isValidNetworkId`**: Removed duplicate export from `utils.ts`, canonical version in `config/networks.ts`.
29
+
30
+ ### Added
31
+ - **`setTrustedOrigins(origins)`**: New method to configure allowed parent-frame origins for iframe/bridge communication.
32
+
33
+ ### Documentation
34
+ - Fixed all event listener examples to use `event.data.xxx` (WalletEvent wrapper)
35
+ - Fixed `TransactionHistory` type (`totalCount`/`hasMore` instead of `total`/`limit`/`totalPages`)
36
+ - Fixed `retry()` docs (positional args, not options object)
37
+ - Fixed `formatZeroXIO` return value (no " OCT" suffix)
38
+ - Fixed `toMicroZeroXIO` return type (string, not number)
39
+ - Removed nonexistent `ConnectOptions.timeout`, `.requestPrivateAccess`
40
+ - Removed nonexistent `ConnectionInfo.permissions`
41
+ - Changed `ErrorCode.TIMEOUT` to `ErrorCode.NETWORK_ERROR`
42
+ - Updated mainnet privacy support to "Yes"
43
+
5
44
  ## [2.4.0] - 2026-03-24
6
45
 
7
46
  ### Added
package/README.md CHANGED
@@ -1,10 +1,22 @@
1
1
  # 0xio Wallet SDK
2
2
 
3
- **Version:** 2.4.0
3
+ **Version:** 2.4.2
4
4
 
5
5
  Official TypeScript SDK for integrating DApps with 0xio Wallet on Octra Network.
6
6
 
7
- ## What's New in v2.4.0
7
+ ## What's New in v2.4.2
8
+
9
+ - **Cross-origin iframe bridge**: Localhost origins now accepted as trusted parents for dev/testing DApp browser scenarios.
10
+ - **Parent origin capture**: `walletReady` signal carries `parentOrigin` for reliable cross-origin reply targeting.
11
+ - **postMessage fix**: Replies to parent frame use captured origin instead of `window.location.origin` (which fails cross-port).
12
+
13
+ ## v2.4.1
14
+
15
+ - **No retry on user rejection**: Transactions, contract calls, and sign requests rejected by the user no longer trigger automatic retry. Prevents double confirmation popups.
16
+ - **Security hardening**: postMessage origin validation, removed iframe auto-trust, response ID binding.
17
+ - **Type fixes**: Replaced `NodeJS.Timeout` with `ReturnType<typeof setTimeout>` for browser compatibility.
18
+
19
+ ## v2.4.0
8
20
 
9
21
  - **Desktop/Mobile DApp Bridge**: SDK now supports running inside iframes (0xio Desktop) and WebViews (0xio App). Requests are relayed to the parent frame automatically.
10
22
  - **Auto Frame Detection**: When `window.parent !== window`, the SDK assumes a wallet bridge is available and marks the wallet as detected.
@@ -163,11 +175,11 @@ console.log('Signature:', signature);
163
175
  ### Events
164
176
 
165
177
  ```typescript
166
- wallet.on('connect', (event) => console.log('Connected:', event.address));
178
+ wallet.on('connect', (event) => console.log('Connected:', event.data.address));
167
179
  wallet.on('disconnect', (event) => console.log('Disconnected'));
168
- wallet.on('balanceChanged', (event) => console.log('New balance:', event.newBalance.total));
169
- wallet.on('accountChanged', (event) => console.log('Account changed:', event.newAddress));
170
- wallet.on('networkChanged', (event) => console.log('Network:', event.newNetwork.name));
180
+ wallet.on('balanceChanged', (event) => console.log('New balance:', event.data.newBalance.total));
181
+ wallet.on('accountChanged', (event) => console.log('Account changed:', event.data.newAddress));
182
+ wallet.on('networkChanged', (event) => console.log('Network:', event.data.newNetwork.name));
171
183
  ```
172
184
 
173
185
  ## Error Handling
@@ -227,7 +239,7 @@ console.log(mainnet.supportsPrivacy); // true
227
239
 
228
240
  | Network | Privacy (FHE) | Explorer |
229
241
  |---------|:---:|---|
230
- | Mainnet Alpha | No | [octrascan.io](https://octrascan.io) |
242
+ | Mainnet Alpha | Yes | [octrascan.io](https://octrascan.io) |
231
243
  | Devnet | Yes | [devnet.octrascan.io](https://devnet.octrascan.io) |
232
244
 
233
245
  ### NetworkInfo Type
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.2
416
416
  * @license MIT
417
417
  */
418
418
 
@@ -452,6 +452,12 @@ 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;
459
+ /** Parent origin learned from walletReady signal */
460
+ private _parentOrigin;
455
461
  /** Maximum number of concurrent pending requests */
456
462
  private readonly MAX_CONCURRENT_REQUESTS;
457
463
  /** Time window for rate limiting (milliseconds) */
@@ -465,7 +471,12 @@ declare class ExtensionCommunicator extends EventEmitter {
465
471
  *
466
472
  * @param {boolean} debug - Enable debug logging
467
473
  */
468
- constructor(debug?: boolean);
474
+ constructor(debug?: boolean, trustedOrigins?: string[]);
475
+ /**
476
+ * Add trusted origins for iframe/bridge communication
477
+ * Call this before connecting if your dApp runs inside a trusted frame
478
+ */
479
+ setTrustedOrigins(origins: string[]): void;
469
480
  /**
470
481
  * Initialize communication with the wallet extension
471
482
  *
@@ -614,6 +625,10 @@ declare function getNetworkConfig(networkId?: string): NetworkInfo;
614
625
  * Get all available networks
615
626
  */
616
627
  declare function getAllNetworks(): NetworkInfo[];
628
+ /**
629
+ * Check if network ID is valid
630
+ */
631
+ declare function isValidNetworkId(networkId: string): boolean;
617
632
 
618
633
  /**
619
634
  * Default balance structure
@@ -623,7 +638,7 @@ declare function createDefaultBalance(total?: number): Balance;
623
638
  * SDK Configuration constants
624
639
  */
625
640
  declare const SDK_CONFIG: {
626
- readonly version: "2.3.0";
641
+ readonly version: "2.4.2";
627
642
  readonly defaultNetworkId: "mainnet";
628
643
  readonly communicationTimeout: 30000;
629
644
  readonly retryAttempts: 3;
@@ -647,10 +662,6 @@ declare function isValidAddress(address: string): boolean;
647
662
  * Validate transaction amount
648
663
  */
649
664
  declare function isValidAmount(amount: number): boolean;
650
- /**
651
- * Validate network ID
652
- */
653
- declare function isValidNetworkId(networkId: string): boolean;
654
665
  /**
655
666
  * Validate transaction message
656
667
  */
@@ -750,7 +761,7 @@ declare function createLogger(prefix: string, debug: boolean): {
750
761
  groupEnd: () => void;
751
762
  };
752
763
 
753
- declare const SDK_VERSION = "2.3.0";
764
+ declare const SDK_VERSION = "2.4.2";
754
765
  declare const MIN_EXTENSION_VERSION = "2.0.1";
755
766
  declare const MIN_EXTENSION_VERSION_DEVNET = "2.2.1";
756
767
  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
@@ -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 globalThis !== 'undefined' && globalThis.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.2
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,12 @@ 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 = [];
519
+ /** Parent origin learned from walletReady signal */
520
+ this._parentOrigin = null;
514
521
  // Rate limiting configuration
515
522
  /** Maximum number of concurrent pending requests */
516
523
  this.MAX_CONCURRENT_REQUESTS = 50;
@@ -521,9 +528,17 @@ class ExtensionCommunicator extends EventEmitter {
521
528
  /** Timestamps of recent requests for rate limiting */
522
529
  this.requestTimestamps = [];
523
530
  this.logger = createLogger('ExtensionCommunicator', debug);
531
+ this.trustedOrigins = trustedOrigins;
524
532
  this.setupMessageListener();
525
533
  this.startExtensionDetection();
526
534
  }
535
+ /**
536
+ * Add trusted origins for iframe/bridge communication
537
+ * Call this before connecting if your dApp runs inside a trusted frame
538
+ */
539
+ setTrustedOrigins(origins) {
540
+ this.trustedOrigins = origins;
541
+ }
527
542
  /**
528
543
  * Initialize communication with the wallet extension
529
544
  *
@@ -649,34 +664,52 @@ class ExtensionCommunicator extends EventEmitter {
649
664
  return; // Not in browser environment
650
665
  }
651
666
  const allowedOrigin = window.location.origin;
652
- window.addEventListener('message', (event) => {
653
- // Accept messages from same origin OR from parent frame (desktop/mobile bridge)
667
+ // Store allowed origins for parent frame communication
668
+ // Desktop (Tauri): tauri://localhost or https://tauri.localhost
669
+ // Mobile (Expo): about:blank or custom scheme
670
+ const trustedParentOrigins = new Set([
671
+ allowedOrigin,
672
+ 'tauri://localhost',
673
+ 'https://tauri.localhost',
674
+ 'http://localhost',
675
+ 'https://localhost',
676
+ ]);
677
+ // Allow dApps to register additional trusted origins
678
+ if (this.trustedOrigins) {
679
+ for (const origin of this.trustedOrigins) {
680
+ trustedParentOrigins.add(origin);
681
+ }
682
+ }
683
+ this.messageListener = (event) => {
684
+ // Strict origin validation
654
685
  const isFromSameOrigin = event.origin === allowedOrigin;
655
- const isFromParent = event.source === window.parent && window.parent !== window;
656
- if (!isFromSameOrigin && !isFromParent) {
686
+ const isLocalhost = event.origin.startsWith('http://localhost:') || event.origin.startsWith('http://127.0.0.1:');
687
+ const isFromTrustedParent = event.source === window.parent
688
+ && window.parent !== window
689
+ && (trustedParentOrigins.has(event.origin) || isLocalhost);
690
+ if (!isFromSameOrigin && !isFromTrustedParent) {
657
691
  return;
658
692
  }
659
- // Accept from same window or parent frame
693
+ // Only accept from same window (extension content script) or trusted parent frame
660
694
  if (event.source !== window && event.source !== window.parent) {
661
695
  return;
662
696
  }
663
- // Check if it's a 0xio SDK response
697
+ // Verify message structure
664
698
  if (!event.data || event.data.source !== '0xio-sdk-bridge') {
665
699
  return;
666
700
  }
667
- // Handle different types of messages
701
+ // Validate response has a pending request (prevents forged responses)
668
702
  if (event.data.response) {
669
- // Regular request/response
670
703
  const response = event.data.response;
671
- if (response && response.id) {
704
+ if (response && response.id && this.pendingRequests.has(response.id)) {
672
705
  this.handleExtensionResponse(response);
673
706
  }
674
707
  }
675
708
  else if (event.data.event) {
676
- // Event notification from extension
677
709
  this.handleExtensionEvent(event.data.event);
678
710
  }
679
- });
711
+ };
712
+ window.addEventListener('message', this.messageListener);
680
713
  this.logger.log('Message listener setup complete');
681
714
  }
682
715
  /**
@@ -732,9 +765,21 @@ class ExtensionCommunicator extends EventEmitter {
732
765
  const msg = { source: '0xio-sdk-request', request };
733
766
  // Post to same window (extension content script picks it up)
734
767
  window.postMessage(msg, window.location.origin);
735
- // Also post to parent frame if in an iframe (desktop/mobile bridge picks it up)
768
+ // Also post to parent frame if in an iframe (desktop/mobile bridge)
736
769
  if (window.parent !== window) {
737
- window.parent.postMessage(msg, '*');
770
+ // Use the parent origin we learned from the walletReady signal, or wildcard
771
+ // Response validation is done by request ID matching, not origin
772
+ const parentOrigin = this._parentOrigin || '*';
773
+ try {
774
+ window.parent.postMessage(msg, parentOrigin);
775
+ }
776
+ catch {
777
+ // Fallback to wildcard if specific origin fails
778
+ try {
779
+ window.parent.postMessage(msg, '*');
780
+ }
781
+ catch { }
782
+ }
738
783
  }
739
784
  }
740
785
  /**
@@ -830,17 +875,33 @@ class ExtensionCommunicator extends EventEmitter {
830
875
  this.logger.log('Received octraWalletReady event');
831
876
  this.isExtensionAvailableState = true;
832
877
  });
833
- // Listen for desktop/mobile bridge walletReady via postMessage
878
+ // Listen for desktop/mobile bridge walletReady via postMessage (with origin validation)
834
879
  window.addEventListener('message', (event) => {
835
880
  if (event.data?.source === '0xio-sdk-bridge' && event.data?.event?.type === 'walletReady') {
836
- this.logger.log('Received walletReady via postMessage (desktop/mobile bridge)');
837
- this.isExtensionAvailableState = true;
881
+ const isSameOrigin = event.origin === window.location.origin;
882
+ const isLocalhost = event.origin.startsWith('http://localhost:') || event.origin.startsWith('http://127.0.0.1:');
883
+ const isTauri = event.origin === 'tauri://localhost' || event.origin === 'https://tauri.localhost';
884
+ const isTrusted = this.trustedOrigins.includes(event.origin) || isTauri || isLocalhost;
885
+ if (isSameOrigin || isTrusted) {
886
+ this.logger.log('Received walletReady via postMessage from trusted origin');
887
+ this.isExtensionAvailableState = true;
888
+ // Capture parent origin for cross-origin reply targeting
889
+ if (event.data.parentOrigin) {
890
+ this._parentOrigin = event.data.parentOrigin;
891
+ }
892
+ else if (event.origin && event.origin !== 'null') {
893
+ this._parentOrigin = event.origin;
894
+ }
895
+ }
896
+ else {
897
+ this.logger.warn(`Ignored walletReady from untrusted origin: ${event.origin}`);
898
+ }
838
899
  }
839
900
  });
840
- // Also detect if running inside a frame with a wallet bridge parent
901
+ // If running inside a frame, do NOT auto-trust the parent.
902
+ // Wait for a walletReady message from a trusted origin instead.
841
903
  if (window.parent !== window) {
842
- this.logger.log('Running inside a frame — checking for parent wallet bridge');
843
- this.isExtensionAvailableState = true;
904
+ this.logger.log('Running inside a frame — waiting for trusted walletReady signal');
844
905
  }
845
906
  // Initial check (in case extension was already injected)
846
907
  this.checkExtensionAvailability();
@@ -977,6 +1038,11 @@ class ExtensionCommunicator extends EventEmitter {
977
1038
  this.pendingRequests.clear();
978
1039
  this.isInitialized = false;
979
1040
  this.isExtensionAvailableState = false;
1041
+ // Remove message listener to prevent accumulation
1042
+ if (this.messageListener) {
1043
+ window.removeEventListener('message', this.messageListener);
1044
+ this.messageListener = null;
1045
+ }
980
1046
  // Call parent cleanup
981
1047
  this.removeAllListeners();
982
1048
  this.logger.log('Communication cleanup complete');
@@ -1050,6 +1116,12 @@ function getNetworkConfig(networkId = DEFAULT_NETWORK_ID) {
1050
1116
  function getAllNetworks() {
1051
1117
  return Object.values(NETWORKS);
1052
1118
  }
1119
+ /**
1120
+ * Check if network ID is valid
1121
+ */
1122
+ function isValidNetworkId(networkId) {
1123
+ return networkId in NETWORKS;
1124
+ }
1053
1125
 
1054
1126
  /**
1055
1127
  * SDK Configuration
@@ -1069,7 +1141,7 @@ function createDefaultBalance(total = 0) {
1069
1141
  * SDK Configuration constants
1070
1142
  */
1071
1143
  const SDK_CONFIG = {
1072
- version: '2.3.0',
1144
+ version: '2.4.2',
1073
1145
  defaultNetworkId: DEFAULT_NETWORK_ID,
1074
1146
  communicationTimeout: 30000, // 30 seconds
1075
1147
  retryAttempts: 3,
@@ -1770,7 +1842,7 @@ var wallet = /*#__PURE__*/Object.freeze({
1770
1842
  */
1771
1843
  // Main exports
1772
1844
  // Version information
1773
- const SDK_VERSION = '2.3.0';
1845
+ const SDK_VERSION = '2.4.2';
1774
1846
  const MIN_EXTENSION_VERSION = '2.0.1'; // Mainnet Alpha
1775
1847
  const MIN_EXTENSION_VERSION_DEVNET = '2.2.1'; // Devnet (contract calls, privacy)
1776
1848
  const SUPPORTED_EXTENSION_VERSIONS = '^2.0.1'; // Supports all versions >= 2.0.1
@@ -1825,7 +1897,8 @@ if (typeof window !== 'undefined') {
1825
1897
  window.__OCTRA_SDK_VERSION__ = SDK_VERSION;
1826
1898
  window.__ZEROXIO_SDK_VERSION__ = SDK_VERSION;
1827
1899
  // Development mode detection
1828
- const isDevelopment = window.location.hostname === 'localhost' ||
1900
+ const isDevelopment = (typeof globalThis !== 'undefined' && globalThis.process?.env?.NODE_ENV === 'development') ||
1901
+ window.location.hostname === 'localhost' ||
1829
1902
  window.location.hostname === '127.0.0.1';
1830
1903
  if (isDevelopment) {
1831
1904
  // Set debug flag but don't automatically log
@@ -1850,13 +1923,7 @@ if (typeof window !== 'undefined') {
1850
1923
  debugMode: !!window.__ZEROXIO_SDK_DEBUG__,
1851
1924
  environment: isDevelopment ? 'development' : 'production'
1852
1925
  }),
1853
- simulateExtensionEvent: (eventType, data) => {
1854
- window.postMessage({
1855
- source: '0xio-sdk-bridge',
1856
- event: { type: eventType, data }
1857
- }, window.location.origin);
1858
- console.log('[0xio SDK] Simulated extension event:', eventType, data);
1859
- },
1926
+ // simulateExtensionEvent removed for security — could be exploited on staging builds
1860
1927
  showWelcome: () => {
1861
1928
  console.log(`[0xio SDK] Development mode - SDK v${SDK_VERSION}`);
1862
1929
  console.log('[0xio SDK] Debug utilities available at window.__ZEROXIO_SDK_UTILS__');