@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 +55 -1
- package/README.md +31 -16
- package/dist/index.d.ts +17 -8
- package/dist/index.esm.js +136 -58
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +136 -58
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +136 -58
- package/dist/index.umd.js.map +1 -1
- package/package.json +2 -2
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
|
|
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
|
+
**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.
|
|
7
|
+
## What's New in v2.4.1
|
|
8
8
|
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
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); //
|
|
227
|
-
console.log(mainnet.supportsPrivacy); //
|
|
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 |
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
327
|
-
|
|
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
|
|
398
|
-
rpcUrl: '
|
|
399
|
-
explorerUrl: 'https://octrascan.io/
|
|
400
|
-
explorerAddressUrl: 'https://octrascan.io/
|
|
401
|
-
indexerUrl: 'https://
|
|
402
|
-
supportsPrivacy:
|
|
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' && (
|
|
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.
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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
|
|
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
|
-
//
|
|
694
|
+
// Verify message structure
|
|
663
695
|
if (!event.data || event.data.source !== '0xio-sdk-bridge') {
|
|
664
696
|
return;
|
|
665
697
|
}
|
|
666
|
-
//
|
|
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
|
-
|
|
732
|
-
|
|
733
|
-
window.postMessage(
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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
|
|
991
|
-
rpcUrl: '
|
|
992
|
-
explorerUrl: 'https://octrascan.io/
|
|
993
|
-
explorerAddressUrl: 'https://octrascan.io/
|
|
994
|
-
indexerUrl: 'https://
|
|
995
|
-
supportsPrivacy:
|
|
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.
|
|
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.
|
|
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 =
|
|
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
|
|
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__');
|