@0xio/sdk 2.7.0 → 2.7.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 +25 -0
- package/README.md +33 -2
- package/dist/index.d.ts +40 -157
- package/dist/index.esm.js +134 -265
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +134 -265
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +134 -265
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,27 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* 0xio Wallet SDK - Event System
|
|
5
|
-
* Type-safe event emitter for wallet events
|
|
6
|
-
*/
|
|
7
3
|
class EventEmitter {
|
|
8
|
-
constructor(
|
|
4
|
+
constructor(_debug = false) {
|
|
9
5
|
this.listeners = new Map();
|
|
10
|
-
this.debug = debug;
|
|
11
6
|
}
|
|
12
|
-
/**
|
|
13
|
-
* Add event listener
|
|
14
|
-
*/
|
|
15
7
|
on(eventType, listener) {
|
|
16
8
|
if (!this.listeners.has(eventType)) {
|
|
17
9
|
this.listeners.set(eventType, new Set());
|
|
18
10
|
}
|
|
19
11
|
this.listeners.get(eventType).add(listener);
|
|
20
|
-
if (this.debug) ;
|
|
21
12
|
}
|
|
22
|
-
/**
|
|
23
|
-
* Remove event listener
|
|
24
|
-
*/
|
|
25
13
|
off(eventType, listener) {
|
|
26
14
|
const eventListeners = this.listeners.get(eventType);
|
|
27
15
|
if (eventListeners) {
|
|
@@ -29,12 +17,8 @@ class EventEmitter {
|
|
|
29
17
|
if (eventListeners.size === 0) {
|
|
30
18
|
this.listeners.delete(eventType);
|
|
31
19
|
}
|
|
32
|
-
if (this.debug) ;
|
|
33
20
|
}
|
|
34
21
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Add one-time event listener
|
|
37
|
-
*/
|
|
38
22
|
once(eventType, listener) {
|
|
39
23
|
const onceListener = (event) => {
|
|
40
24
|
// Remove BEFORE calling so a throwing listener doesn't stay registered
|
|
@@ -43,9 +27,6 @@ class EventEmitter {
|
|
|
43
27
|
};
|
|
44
28
|
this.on(eventType, onceListener);
|
|
45
29
|
}
|
|
46
|
-
/**
|
|
47
|
-
* Emit event to all listeners
|
|
48
|
-
*/
|
|
49
30
|
emit(eventType, data) {
|
|
50
31
|
const event = {
|
|
51
32
|
type: eventType,
|
|
@@ -54,49 +35,32 @@ class EventEmitter {
|
|
|
54
35
|
};
|
|
55
36
|
const eventListeners = this.listeners.get(eventType);
|
|
56
37
|
if (eventListeners && eventListeners.size > 0) {
|
|
57
|
-
if
|
|
58
|
-
|
|
59
|
-
const listenersArray = Array.from(eventListeners);
|
|
60
|
-
for (const listener of listenersArray) {
|
|
38
|
+
// snapshot to avoid issues if listeners modify the set during iteration
|
|
39
|
+
for (const listener of Array.from(eventListeners)) {
|
|
61
40
|
try {
|
|
62
41
|
listener(event);
|
|
63
42
|
}
|
|
64
|
-
catch
|
|
65
|
-
//
|
|
43
|
+
catch {
|
|
44
|
+
// listener errors are swallowed to keep the event loop running
|
|
66
45
|
}
|
|
67
46
|
}
|
|
68
47
|
}
|
|
69
|
-
else if (this.debug) ;
|
|
70
48
|
}
|
|
71
|
-
/**
|
|
72
|
-
* Remove all listeners for a specific event type
|
|
73
|
-
*/
|
|
74
49
|
removeAllListeners(eventType) {
|
|
75
50
|
if (eventType) {
|
|
76
51
|
this.listeners.delete(eventType);
|
|
77
|
-
if (this.debug) ;
|
|
78
52
|
}
|
|
79
53
|
else {
|
|
80
54
|
this.listeners.clear();
|
|
81
|
-
if (this.debug) ;
|
|
82
55
|
}
|
|
83
56
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Get number of listeners for an event type
|
|
86
|
-
*/
|
|
87
57
|
listenerCount(eventType) {
|
|
88
58
|
const eventListeners = this.listeners.get(eventType);
|
|
89
59
|
return eventListeners ? eventListeners.size : 0;
|
|
90
60
|
}
|
|
91
|
-
/**
|
|
92
|
-
* Get all event types that have listeners
|
|
93
|
-
*/
|
|
94
61
|
eventTypes() {
|
|
95
62
|
return Array.from(this.listeners.keys());
|
|
96
63
|
}
|
|
97
|
-
/**
|
|
98
|
-
* Check if there are any listeners for an event type
|
|
99
|
-
*/
|
|
100
64
|
hasListeners(eventType) {
|
|
101
65
|
return this.listenerCount(eventType) > 0;
|
|
102
66
|
}
|
|
@@ -269,16 +233,6 @@ function createZeroXIOAdapter(extraTrustedOrigins = []) {
|
|
|
269
233
|
/** Default 0xio adapter instance (no extra trusted origins). */
|
|
270
234
|
const ZeroXIOAdapter = createZeroXIOAdapter();
|
|
271
235
|
|
|
272
|
-
/**
|
|
273
|
-
* 0xio Wallet SDK - Utilities
|
|
274
|
-
* Helper functions for validation, formatting, and common operations
|
|
275
|
-
*/
|
|
276
|
-
// ===================
|
|
277
|
-
// VALIDATION UTILITIES
|
|
278
|
-
// ===================
|
|
279
|
-
/**
|
|
280
|
-
* Validate wallet address for Octra blockchain
|
|
281
|
-
*/
|
|
282
236
|
function isValidAddress(address) {
|
|
283
237
|
if (!address || typeof address !== 'string') {
|
|
284
238
|
return false;
|
|
@@ -302,9 +256,6 @@ function isValidAmount(amount) {
|
|
|
302
256
|
Number.isFinite(amount) &&
|
|
303
257
|
amount <= Number.MAX_SAFE_INTEGER;
|
|
304
258
|
}
|
|
305
|
-
/**
|
|
306
|
-
* Validate transaction message
|
|
307
|
-
*/
|
|
308
259
|
function isValidMessage(message) {
|
|
309
260
|
// Type check first — falsy non-strings (0, false, null) are NOT valid messages
|
|
310
261
|
if (typeof message !== 'string') {
|
|
@@ -317,15 +268,9 @@ function isValidMessage(message) {
|
|
|
317
268
|
// 100KB limit — contract call params can be large (serialized JSON)
|
|
318
269
|
return message.length <= 100000;
|
|
319
270
|
}
|
|
320
|
-
/**
|
|
321
|
-
* Validate fee level
|
|
322
|
-
*/
|
|
323
271
|
function isValidFeeLevel(feeLevel) {
|
|
324
272
|
return feeLevel === 1 || feeLevel === 3;
|
|
325
273
|
}
|
|
326
|
-
// ===================
|
|
327
|
-
// ADDRESS DERIVATION
|
|
328
|
-
// ===================
|
|
329
274
|
const _B58_ALPHA = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
330
275
|
function _base58Encode(buf) {
|
|
331
276
|
let zeros = 0;
|
|
@@ -370,12 +315,6 @@ async function deriveOctraAddress(publicKeyBase64) {
|
|
|
370
315
|
const hashBuf = await crypto.subtle.digest('SHA-256', bytes);
|
|
371
316
|
return 'oct' + _base58Encode(new Uint8Array(hashBuf));
|
|
372
317
|
}
|
|
373
|
-
// ===================
|
|
374
|
-
// FORMATTING UTILITIES
|
|
375
|
-
// ===================
|
|
376
|
-
/**
|
|
377
|
-
* Format OCT amount for display
|
|
378
|
-
*/
|
|
379
318
|
function formatOCT(amount, decimals = 6) {
|
|
380
319
|
const n = typeof amount === 'string' ? parseFloat(amount) : amount;
|
|
381
320
|
if (!isValidAmount(n)) {
|
|
@@ -386,9 +325,6 @@ function formatOCT(amount, decimals = 6) {
|
|
|
386
325
|
maximumFractionDigits: decimals
|
|
387
326
|
});
|
|
388
327
|
}
|
|
389
|
-
/**
|
|
390
|
-
* Format address for display (truncated)
|
|
391
|
-
*/
|
|
392
328
|
function formatAddress(address, prefixLength = 6, suffixLength = 4) {
|
|
393
329
|
if (!isValidAddress(address)) {
|
|
394
330
|
return 'Invalid Address';
|
|
@@ -398,16 +334,10 @@ function formatAddress(address, prefixLength = 6, suffixLength = 4) {
|
|
|
398
334
|
}
|
|
399
335
|
return `${address.slice(0, prefixLength)}...${address.slice(-suffixLength)}`;
|
|
400
336
|
}
|
|
401
|
-
/**
|
|
402
|
-
* Format timestamp for display
|
|
403
|
-
*/
|
|
404
337
|
function formatTimestamp(timestamp) {
|
|
405
338
|
const date = new Date(timestamp);
|
|
406
339
|
return date.toLocaleString();
|
|
407
340
|
}
|
|
408
|
-
/**
|
|
409
|
-
* Format transaction hash for display
|
|
410
|
-
*/
|
|
411
341
|
function formatTxHash(hash, length = 12) {
|
|
412
342
|
if (!hash || typeof hash !== 'string') {
|
|
413
343
|
return 'Invalid Hash';
|
|
@@ -419,12 +349,6 @@ function formatTxHash(hash, length = 12) {
|
|
|
419
349
|
const suffixLength = Math.floor(length / 2);
|
|
420
350
|
return `${hash.slice(0, prefixLength)}...${hash.slice(-suffixLength)}`;
|
|
421
351
|
}
|
|
422
|
-
// ===================
|
|
423
|
-
// CONVERSION UTILITIES
|
|
424
|
-
// ===================
|
|
425
|
-
/**
|
|
426
|
-
* Convert OCT to micro OCT (for network transmission)
|
|
427
|
-
*/
|
|
428
352
|
function toMicroOCT(amount) {
|
|
429
353
|
if (!isValidAmount(amount)) {
|
|
430
354
|
throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_AMOUNT, 'Invalid amount for conversion');
|
|
@@ -433,9 +357,6 @@ function toMicroOCT(amount) {
|
|
|
433
357
|
const microOCT = Math.round(amount * 1000000);
|
|
434
358
|
return microOCT.toString();
|
|
435
359
|
}
|
|
436
|
-
/**
|
|
437
|
-
* Convert micro OCT to OCT (for display)
|
|
438
|
-
*/
|
|
439
360
|
function fromMicroOCT(microAmount) {
|
|
440
361
|
const amount = typeof microAmount === 'string' ? parseInt(microAmount, 10) : microAmount;
|
|
441
362
|
if (!Number.isFinite(amount) || amount < 0) {
|
|
@@ -443,12 +364,6 @@ function fromMicroOCT(microAmount) {
|
|
|
443
364
|
}
|
|
444
365
|
return amount / 1000000;
|
|
445
366
|
}
|
|
446
|
-
// ===================
|
|
447
|
-
// ERROR UTILITIES
|
|
448
|
-
// ===================
|
|
449
|
-
/**
|
|
450
|
-
* Create standardized error messages
|
|
451
|
-
*/
|
|
452
367
|
function createErrorMessage(code, context) {
|
|
453
368
|
const baseMessages = {
|
|
454
369
|
[exports.ErrorCode.EXTENSION_NOT_FOUND]: '0xio Wallet extension is not installed or enabled',
|
|
@@ -475,24 +390,12 @@ function createErrorMessage(code, context) {
|
|
|
475
390
|
const baseMessage = baseMessages[code] || 'Unknown error';
|
|
476
391
|
return context ? `${baseMessage}: ${context}` : baseMessage;
|
|
477
392
|
}
|
|
478
|
-
/**
|
|
479
|
-
* Check if error is a specific type
|
|
480
|
-
*/
|
|
481
393
|
function isErrorType(error, code) {
|
|
482
394
|
return error instanceof ZeroXIOWalletError && error.code === code;
|
|
483
395
|
}
|
|
484
|
-
// ===================
|
|
485
|
-
// ASYNC UTILITIES
|
|
486
|
-
// ===================
|
|
487
|
-
/**
|
|
488
|
-
* Create a promise that resolves after a delay
|
|
489
|
-
*/
|
|
490
396
|
function delay(ms) {
|
|
491
397
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
492
398
|
}
|
|
493
|
-
/**
|
|
494
|
-
* Retry an async operation with exponential backoff
|
|
495
|
-
*/
|
|
496
399
|
async function retry(operation, maxRetries = 3, baseDelay = 1000) {
|
|
497
400
|
let lastError;
|
|
498
401
|
// maxRetries = number of retries AFTER the first attempt
|
|
@@ -532,18 +435,9 @@ function withTimeout(promise, timeoutMs, timeoutMessage = 'Operation timed out')
|
|
|
532
435
|
clearTimeout(timer);
|
|
533
436
|
});
|
|
534
437
|
}
|
|
535
|
-
// ===================
|
|
536
|
-
// BROWSER UTILITIES
|
|
537
|
-
// ===================
|
|
538
|
-
/**
|
|
539
|
-
* Check if running in browser environment
|
|
540
|
-
*/
|
|
541
438
|
function isBrowser() {
|
|
542
439
|
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
543
440
|
}
|
|
544
|
-
/**
|
|
545
|
-
* Check if browser supports required features
|
|
546
|
-
*/
|
|
547
441
|
function checkBrowserSupport() {
|
|
548
442
|
const missingFeatures = [];
|
|
549
443
|
if (!isBrowser()) {
|
|
@@ -565,12 +459,6 @@ function checkBrowserSupport() {
|
|
|
565
459
|
missingFeatures
|
|
566
460
|
};
|
|
567
461
|
}
|
|
568
|
-
// ===================
|
|
569
|
-
// DEVELOPMENT UTILITIES
|
|
570
|
-
// ===================
|
|
571
|
-
/**
|
|
572
|
-
* Generate mock data for development/testing
|
|
573
|
-
*/
|
|
574
462
|
function generateMockData() {
|
|
575
463
|
return {
|
|
576
464
|
address: 'oct' + Math.random().toString(36).substring(2, 22) + Math.random().toString(36).substring(2, 26),
|
|
@@ -593,9 +481,6 @@ function generateMockData() {
|
|
|
593
481
|
}
|
|
594
482
|
};
|
|
595
483
|
}
|
|
596
|
-
/**
|
|
597
|
-
* Create development logger
|
|
598
|
-
*/
|
|
599
484
|
function createLogger(prefix, debug) {
|
|
600
485
|
const isDevelopment = typeof window !== 'undefined' && ((typeof globalThis !== 'undefined' && globalThis.process?.env?.NODE_ENV === 'development') ||
|
|
601
486
|
window.location.hostname === 'localhost' ||
|
|
@@ -648,17 +533,6 @@ function createLogger(prefix, debug) {
|
|
|
648
533
|
};
|
|
649
534
|
}
|
|
650
535
|
|
|
651
|
-
/**
|
|
652
|
-
* 0xio Wallet SDK - Extension Communication Module
|
|
653
|
-
*
|
|
654
|
-
* @fileoverview Manages secure communication between the SDK and browser extension.
|
|
655
|
-
* Implements message passing, request/response handling, rate limiting, and origin validation
|
|
656
|
-
* to ensure secure wallet interactions.
|
|
657
|
-
*
|
|
658
|
-
* @module communication
|
|
659
|
-
* @version 2.7.0
|
|
660
|
-
* @license MIT
|
|
661
|
-
*/
|
|
662
536
|
class ExtensionCommunicator extends EventEmitter {
|
|
663
537
|
constructor(debug = false, trustedOrigins = [], adapter) {
|
|
664
538
|
super(debug);
|
|
@@ -909,9 +783,12 @@ class ExtensionCommunicator extends EventEmitter {
|
|
|
909
783
|
return;
|
|
910
784
|
}
|
|
911
785
|
const isSameOrigin = event.origin === window.location.origin;
|
|
912
|
-
const isLocalhost = event.origin.startsWith('http://localhost:') || event.origin.startsWith('http://127.0.0.1:');
|
|
913
786
|
const isTauri = event.origin === 'tauri://localhost' || event.origin === 'https://tauri.localhost';
|
|
914
|
-
const
|
|
787
|
+
const hasExplicitList = this.trustedOrigins.length > 0;
|
|
788
|
+
// When trustedParentOrigins is explicitly set, implicit localhost trust is disabled
|
|
789
|
+
const isLocalhostAllowed = !hasExplicitList &&
|
|
790
|
+
(event.origin.startsWith('http://localhost:') || event.origin.startsWith('http://127.0.0.1:'));
|
|
791
|
+
const isTrustedOrigin = this.trustedOrigins.includes(event.origin) || isTauri || isLocalhostAllowed;
|
|
915
792
|
// In iframe mode, only trust the actual parent window
|
|
916
793
|
const inIframe = window.parent !== window;
|
|
917
794
|
if (inIframe && event.source !== window.parent) {
|
|
@@ -1072,6 +949,7 @@ class ExtensionCommunicator extends EventEmitter {
|
|
|
1072
949
|
// causing double popups where the second tx fails (stale nonce/state).
|
|
1073
950
|
ExtensionCommunicator.NO_RETRY_METHODS = new Set([
|
|
1074
951
|
'connect', 'send_transaction', 'call_contract', 'signMessage',
|
|
952
|
+
'sign_transaction', 'broadcast_only',
|
|
1075
953
|
'send_private_transfer', 'claim_private_transfer',
|
|
1076
954
|
'encrypt_balance', 'decrypt_balance',
|
|
1077
955
|
]);
|
|
@@ -1079,8 +957,8 @@ ExtensionCommunicator.INTERACTIVE_METHODS = ExtensionCommunicator.NO_RETRY_METHO
|
|
|
1079
957
|
// only forward known event types
|
|
1080
958
|
ExtensionCommunicator.VALID_EVENT_TYPES = new Set([
|
|
1081
959
|
'connect', 'disconnect', 'accountChanged', 'balanceChanged',
|
|
1082
|
-
'networkChanged', 'transactionConfirmed', '
|
|
1083
|
-
'extensionLocked', 'extensionUnlocked'
|
|
960
|
+
'networkChanged', 'transactionConfirmed', 'permissionsChanged', 'message',
|
|
961
|
+
'error', 'extensionLocked', 'extensionUnlocked'
|
|
1084
962
|
]);
|
|
1085
963
|
|
|
1086
964
|
/**
|
|
@@ -1163,6 +1041,14 @@ function validateNetworkInfo(raw) {
|
|
|
1163
1041
|
return null;
|
|
1164
1042
|
if (typeof raw.rpcUrl !== 'string' || (!raw.rpcUrl && raw.id !== 'custom'))
|
|
1165
1043
|
return null;
|
|
1044
|
+
if (raw.rpcUrl) {
|
|
1045
|
+
const isHttps = raw.rpcUrl.startsWith('https://');
|
|
1046
|
+
const isLocal = raw.rpcUrl.startsWith('http://localhost') || raw.rpcUrl.startsWith('http://127.0.0.1');
|
|
1047
|
+
const isTestnet = raw.isTestnet === true;
|
|
1048
|
+
// Reject plain-http RPC URLs from untrusted sources unless testnet-flagged or local
|
|
1049
|
+
if (!isHttps && !isLocal && !isTestnet)
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
1166
1052
|
if (typeof raw.supportsPrivacy !== 'boolean')
|
|
1167
1053
|
return null;
|
|
1168
1054
|
return Object.freeze({
|
|
@@ -1178,9 +1064,6 @@ function validateNetworkInfo(raw) {
|
|
|
1178
1064
|
});
|
|
1179
1065
|
}
|
|
1180
1066
|
|
|
1181
|
-
/**
|
|
1182
|
-
* SDK Configuration
|
|
1183
|
-
*/
|
|
1184
1067
|
/**
|
|
1185
1068
|
* Default balance structure.
|
|
1186
1069
|
* Accepts a numeric total or undefined — never pass a Balance object here.
|
|
@@ -1216,27 +1099,17 @@ function validateBalance(raw) {
|
|
|
1216
1099
|
currency: 'OCT'
|
|
1217
1100
|
};
|
|
1218
1101
|
}
|
|
1219
|
-
/**
|
|
1220
|
-
* SDK Configuration constants
|
|
1221
|
-
*/
|
|
1222
1102
|
const SDK_CONFIG = {
|
|
1223
|
-
version: '2.7.
|
|
1103
|
+
version: '2.7.1',
|
|
1224
1104
|
defaultNetworkId: DEFAULT_NETWORK_ID,
|
|
1225
1105
|
communicationTimeout: 30000, // 30 seconds
|
|
1226
1106
|
retryAttempts: 3,
|
|
1227
1107
|
retryDelay: 1000, // 1 second
|
|
1228
1108
|
};
|
|
1229
|
-
/**
|
|
1230
|
-
* Get default network configuration
|
|
1231
|
-
*/
|
|
1232
1109
|
function getDefaultNetwork() {
|
|
1233
1110
|
return getNetworkConfig(SDK_CONFIG.defaultNetworkId);
|
|
1234
1111
|
}
|
|
1235
1112
|
|
|
1236
|
-
/**
|
|
1237
|
-
* 0xio Wallet SDK - Main Wallet Class
|
|
1238
|
-
* Primary interface for DApp developers to interact with 0xio Wallet
|
|
1239
|
-
*/
|
|
1240
1113
|
class ZeroXIOWallet extends EventEmitter {
|
|
1241
1114
|
constructor(config) {
|
|
1242
1115
|
super(config.debug);
|
|
@@ -1252,32 +1125,22 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1252
1125
|
debug: config.debug || false
|
|
1253
1126
|
};
|
|
1254
1127
|
this.logger = createLogger('ZeroXIOWallet', this.config.debug || false);
|
|
1255
|
-
this.communicator = new ExtensionCommunicator(this.config.debug, [], this.config.adapter);
|
|
1128
|
+
this.communicator = new ExtensionCommunicator(this.config.debug, this.config.trustedParentOrigins ?? [], this.config.adapter);
|
|
1256
1129
|
this.logger.log('Wallet instance created with config:', this.config);
|
|
1257
1130
|
}
|
|
1258
|
-
// ===================
|
|
1259
|
-
// INITIALIZATION
|
|
1260
|
-
// ===================
|
|
1261
|
-
/**
|
|
1262
|
-
* Initialize the SDK
|
|
1263
|
-
* Must be called before using any other methods
|
|
1264
|
-
*/
|
|
1265
1131
|
async initialize() {
|
|
1266
1132
|
if (this.isInitialized) {
|
|
1267
1133
|
return true;
|
|
1268
1134
|
}
|
|
1269
|
-
// single-flight init
|
|
1270
1135
|
if (this._initPromise) {
|
|
1271
1136
|
return this._initPromise;
|
|
1272
1137
|
}
|
|
1273
1138
|
this._initPromise = (async () => {
|
|
1274
1139
|
try {
|
|
1275
|
-
// Initialize extension communication
|
|
1276
1140
|
const communicationReady = await this.communicator.initialize();
|
|
1277
1141
|
if (!communicationReady) {
|
|
1278
1142
|
throw new ZeroXIOWalletError(exports.ErrorCode.EXTENSION_NOT_FOUND, 'Failed to establish communication with 0xio Wallet extension');
|
|
1279
1143
|
}
|
|
1280
|
-
// Register this DApp with the extension
|
|
1281
1144
|
await this.communicator.sendRequest('register_dapp', {
|
|
1282
1145
|
appName: this.config.appName,
|
|
1283
1146
|
appDescription: this.config.appDescription,
|
|
@@ -1287,7 +1150,6 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1287
1150
|
requiredPermissions: this.config.requiredPermissions,
|
|
1288
1151
|
networkId: this.config.networkId
|
|
1289
1152
|
});
|
|
1290
|
-
// Setup event forwarding from extension
|
|
1291
1153
|
this.setupExtensionEventListeners();
|
|
1292
1154
|
this.isInitialized = true;
|
|
1293
1155
|
this.logger.log('SDK initialized successfully');
|
|
@@ -1306,29 +1168,21 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1306
1168
|
})();
|
|
1307
1169
|
return this._initPromise;
|
|
1308
1170
|
}
|
|
1309
|
-
/**
|
|
1310
|
-
* Check if SDK is initialized
|
|
1311
|
-
*/
|
|
1312
1171
|
isReady() {
|
|
1313
1172
|
return this.isInitialized && this.communicator.isExtensionAvailable();
|
|
1314
1173
|
}
|
|
1315
|
-
// ===================
|
|
1316
|
-
// CONNECTION MANAGEMENT
|
|
1317
|
-
// ===================
|
|
1318
|
-
/**
|
|
1319
|
-
* Connect to wallet
|
|
1320
|
-
*/
|
|
1321
1174
|
async connect(options = {}) {
|
|
1322
1175
|
this.ensureInitialized();
|
|
1323
1176
|
try {
|
|
1324
1177
|
this.logger.log('Attempting to connect with options:', options);
|
|
1325
|
-
// filter to declared perms only
|
|
1178
|
+
// filter to declared perms only — accept both RFC 'permissions' and legacy 'requestPermissions'
|
|
1326
1179
|
const declaredPermissions = this.config.requiredPermissions || [];
|
|
1327
|
-
const
|
|
1328
|
-
|
|
1180
|
+
const requestedPerms = options.permissions ?? options.requestPermissions;
|
|
1181
|
+
const requestedPermissions = requestedPerms
|
|
1182
|
+
? requestedPerms.filter(p => declaredPermissions.includes(p))
|
|
1329
1183
|
: declaredPermissions;
|
|
1330
1184
|
const result = await this.communicator.sendRequest('connect', {
|
|
1331
|
-
|
|
1185
|
+
permissions: requestedPermissions,
|
|
1332
1186
|
networkId: options.networkId || this.config.networkId
|
|
1333
1187
|
});
|
|
1334
1188
|
// verify pubkey→addr binding
|
|
@@ -1345,9 +1199,12 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1345
1199
|
this.logger.warn('Address derivation check skipped (crypto unavailable):', e);
|
|
1346
1200
|
}
|
|
1347
1201
|
}
|
|
1348
|
-
// Use networkInfo from extension response — validate before caching
|
|
1202
|
+
// Use networkInfo from extension response — validate before caching.
|
|
1349
1203
|
const networkInfo = validateNetworkInfo(result.networkInfo)
|
|
1350
|
-
??
|
|
1204
|
+
?? (result.networkId ? getNetworkConfig(result.networkId) : null);
|
|
1205
|
+
if (!networkInfo) {
|
|
1206
|
+
throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Wallet did not return valid network metadata.');
|
|
1207
|
+
}
|
|
1351
1208
|
const permissions = result.permissions || [];
|
|
1352
1209
|
// Update connection info — including permissions
|
|
1353
1210
|
this.connectionInfo = {
|
|
@@ -1440,7 +1297,11 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1440
1297
|
// validate untrusted balance/networkInfo before caching
|
|
1441
1298
|
const balanceInfo = validateBalance(result.balance) ?? createDefaultBalance();
|
|
1442
1299
|
const networkInfo = validateNetworkInfo(result.networkInfo)
|
|
1443
|
-
??
|
|
1300
|
+
?? (result.networkId ? getNetworkConfig(result.networkId) : null);
|
|
1301
|
+
if (!networkInfo) {
|
|
1302
|
+
this.logger.warn('getConnectionStatus: wallet returned no network metadata — returning cached state');
|
|
1303
|
+
return this.connectionInfo;
|
|
1304
|
+
}
|
|
1444
1305
|
const wasConnected = this.connectionInfo.isConnected;
|
|
1445
1306
|
const permissions = result.permissions || [];
|
|
1446
1307
|
// preserve existing connectedAt
|
|
@@ -1513,18 +1374,9 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1513
1374
|
getNetworkId() {
|
|
1514
1375
|
return this.connectionInfo.networkInfo?.id || null;
|
|
1515
1376
|
}
|
|
1516
|
-
// ===================
|
|
1517
|
-
// WALLET INFORMATION
|
|
1518
|
-
// ===================
|
|
1519
|
-
/**
|
|
1520
|
-
* Get current wallet address
|
|
1521
|
-
*/
|
|
1522
1377
|
getAddress() {
|
|
1523
1378
|
return this.connectionInfo.address || null;
|
|
1524
1379
|
}
|
|
1525
|
-
/**
|
|
1526
|
-
* Get current balance
|
|
1527
|
-
*/
|
|
1528
1380
|
async getBalance(forceRefresh = false) {
|
|
1529
1381
|
this.ensureConnected();
|
|
1530
1382
|
try {
|
|
@@ -1549,7 +1401,6 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1549
1401
|
// skip if session changed mid-flight
|
|
1550
1402
|
if (this._sessionVersion !== sv)
|
|
1551
1403
|
return result;
|
|
1552
|
-
// Update cached balance
|
|
1553
1404
|
if (this.connectionInfo.balance) {
|
|
1554
1405
|
const previousBalance = this.connectionInfo.balance;
|
|
1555
1406
|
this.connectionInfo.balance = result;
|
|
@@ -1576,15 +1427,11 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1576
1427
|
throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Failed to get balance', error);
|
|
1577
1428
|
}
|
|
1578
1429
|
}
|
|
1579
|
-
/**
|
|
1580
|
-
* Get network information
|
|
1581
|
-
*/
|
|
1582
1430
|
async getNetworkInfo() {
|
|
1583
1431
|
this.ensureInitialized();
|
|
1584
1432
|
try {
|
|
1585
1433
|
const sv = this._sessionVersion;
|
|
1586
1434
|
const result = await this.communicator.sendRequest('get_network_info');
|
|
1587
|
-
// validate network info before caching
|
|
1588
1435
|
const networkInfo = validateNetworkInfo(result);
|
|
1589
1436
|
if (!networkInfo) {
|
|
1590
1437
|
throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Extension returned invalid network info');
|
|
@@ -1592,11 +1439,9 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1592
1439
|
// skip if session changed mid-flight
|
|
1593
1440
|
if (this._sessionVersion !== sv)
|
|
1594
1441
|
return networkInfo;
|
|
1595
|
-
// Update cached network info
|
|
1596
1442
|
if (this.connectionInfo.networkInfo) {
|
|
1597
1443
|
const previousNetwork = this.connectionInfo.networkInfo;
|
|
1598
1444
|
this.connectionInfo.networkInfo = networkInfo;
|
|
1599
|
-
// Emit network changed event if different
|
|
1600
1445
|
if (previousNetwork.id !== networkInfo.id) {
|
|
1601
1446
|
const networkChangedEvent = {
|
|
1602
1447
|
previousNetwork,
|
|
@@ -1616,22 +1461,14 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1616
1461
|
throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Failed to get network info', error);
|
|
1617
1462
|
}
|
|
1618
1463
|
}
|
|
1619
|
-
// ===================
|
|
1620
|
-
// TRANSACTIONS
|
|
1621
|
-
// ===================
|
|
1622
|
-
/**
|
|
1623
|
-
* Send transaction
|
|
1624
|
-
*/
|
|
1625
1464
|
async sendTransaction(txData) {
|
|
1626
1465
|
this.ensureConnected();
|
|
1627
|
-
// validate inputs
|
|
1628
1466
|
if (!isValidAddress(txData.to)) {
|
|
1629
1467
|
throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_ADDRESS, 'Invalid recipient address');
|
|
1630
1468
|
}
|
|
1631
1469
|
if (!isValidAmount(txData.amount)) {
|
|
1632
1470
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Invalid transaction amount');
|
|
1633
1471
|
}
|
|
1634
|
-
// bound memo
|
|
1635
1472
|
if (txData.message && txData.message.length > 1000) {
|
|
1636
1473
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Transaction message too long (max 1,000 characters)');
|
|
1637
1474
|
}
|
|
@@ -1640,8 +1477,8 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1640
1477
|
this.logger.log('Sending transaction:', { to: txData.to });
|
|
1641
1478
|
const result = await this.communicator.sendRequest('send_transaction', txData);
|
|
1642
1479
|
this.logger.log('Transaction result:', result);
|
|
1643
|
-
// Refresh balance after successful transaction
|
|
1644
|
-
if (result.success) {
|
|
1480
|
+
// Refresh balance after successful transaction (accept RFC 'accepted' or legacy 'success')
|
|
1481
|
+
if (result.accepted ?? result.success) {
|
|
1645
1482
|
setTimeout(() => {
|
|
1646
1483
|
this.getBalance(true).catch(error => {
|
|
1647
1484
|
this.logger.warn('Failed to refresh balance after transaction:', error);
|
|
@@ -1658,23 +1495,70 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1658
1495
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Failed to send transaction', error);
|
|
1659
1496
|
}
|
|
1660
1497
|
}
|
|
1498
|
+
/**
|
|
1499
|
+
* Sign a transaction without broadcasting it (RFC-O-1 octra_signTransaction).
|
|
1500
|
+
* Returns the signed transaction object for manual submission via submitTransaction().
|
|
1501
|
+
*/
|
|
1502
|
+
async signTransaction(txData) {
|
|
1503
|
+
this.ensureConnected();
|
|
1504
|
+
if (!isValidAddress(txData.to)) {
|
|
1505
|
+
throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_ADDRESS, 'Invalid recipient address');
|
|
1506
|
+
}
|
|
1507
|
+
if (!isValidAmount(txData.amount)) {
|
|
1508
|
+
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Invalid transaction amount');
|
|
1509
|
+
}
|
|
1510
|
+
if (txData.message && txData.message.length > 1000) {
|
|
1511
|
+
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Transaction message too long (max 1,000 characters)');
|
|
1512
|
+
}
|
|
1513
|
+
try {
|
|
1514
|
+
this.logger.log('Requesting transaction signature:', { to: txData.to });
|
|
1515
|
+
const result = await this.communicator.sendRequest('sign_transaction', txData);
|
|
1516
|
+
return result;
|
|
1517
|
+
}
|
|
1518
|
+
catch (error) {
|
|
1519
|
+
if (error instanceof ZeroXIOWalletError)
|
|
1520
|
+
throw error;
|
|
1521
|
+
throw new ZeroXIOWalletError(exports.ErrorCode.SIGNATURE_FAILED, 'Failed to sign transaction', error);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Broadcast a pre-signed transaction (RFC-O-1 octra_submitTransaction).
|
|
1526
|
+
* Use after signTransaction() to submit the signed tx to the network.
|
|
1527
|
+
*/
|
|
1528
|
+
async submitTransaction(signedTx) {
|
|
1529
|
+
this.ensureConnected();
|
|
1530
|
+
if (!signedTx || typeof signedTx !== 'object') {
|
|
1531
|
+
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'signedTx must be an object');
|
|
1532
|
+
}
|
|
1533
|
+
try {
|
|
1534
|
+
this.logger.log('Submitting pre-signed transaction');
|
|
1535
|
+
const result = await this.communicator.sendRequest('broadcast_only', { signedTx });
|
|
1536
|
+
return result;
|
|
1537
|
+
}
|
|
1538
|
+
catch (error) {
|
|
1539
|
+
if (error instanceof ZeroXIOWalletError)
|
|
1540
|
+
throw error;
|
|
1541
|
+
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Failed to submit transaction', error);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1661
1544
|
/**
|
|
1662
1545
|
* Call a smart contract method (state-changing).
|
|
1663
1546
|
* The extension builds, signs, and submits the transaction via octra_submit.
|
|
1664
1547
|
*/
|
|
1665
1548
|
async callContract(callData) {
|
|
1666
1549
|
this.ensureConnected();
|
|
1667
|
-
// validate inputs
|
|
1668
1550
|
if (!isValidAddress(callData.contract)) {
|
|
1669
1551
|
throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_ADDRESS, 'Invalid contract address');
|
|
1670
1552
|
}
|
|
1671
1553
|
if (!callData.method || typeof callData.method !== 'string') {
|
|
1672
1554
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Contract method is required');
|
|
1673
1555
|
}
|
|
1674
|
-
// bound method + params size
|
|
1675
1556
|
if (callData.method.length > 200) {
|
|
1676
1557
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Contract method name too long (max 200 characters)');
|
|
1677
1558
|
}
|
|
1559
|
+
if (callData.amount != null) {
|
|
1560
|
+
this.assertExactOCTAmount(callData.amount, 'Contract call amount');
|
|
1561
|
+
}
|
|
1678
1562
|
try {
|
|
1679
1563
|
if (JSON.stringify(callData.params).length > 65536) {
|
|
1680
1564
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Contract params too large (max 64 KB)');
|
|
@@ -1712,14 +1596,12 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1712
1596
|
*/
|
|
1713
1597
|
async contractCallView(viewData) {
|
|
1714
1598
|
this.ensureInitialized();
|
|
1715
|
-
// validate inputs
|
|
1716
1599
|
if (!isValidAddress(viewData.contract)) {
|
|
1717
1600
|
throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_ADDRESS, 'Invalid contract address');
|
|
1718
1601
|
}
|
|
1719
1602
|
if (!viewData.method || typeof viewData.method !== 'string') {
|
|
1720
1603
|
throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Contract method is required');
|
|
1721
1604
|
}
|
|
1722
|
-
// bound method + params size
|
|
1723
1605
|
if (viewData.method.length > 200) {
|
|
1724
1606
|
throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Contract method name too long (max 200 characters)');
|
|
1725
1607
|
}
|
|
@@ -1759,7 +1641,6 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1759
1641
|
*/
|
|
1760
1642
|
async getContractStorage(contract, key) {
|
|
1761
1643
|
this.ensureInitialized();
|
|
1762
|
-
// validate inputs
|
|
1763
1644
|
if (!isValidAddress(contract)) {
|
|
1764
1645
|
throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_ADDRESS, 'Invalid contract address');
|
|
1765
1646
|
}
|
|
@@ -1801,12 +1682,6 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1801
1682
|
throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Failed to get transaction history', error);
|
|
1802
1683
|
}
|
|
1803
1684
|
}
|
|
1804
|
-
// ===================
|
|
1805
|
-
// PRIVATE FEATURES
|
|
1806
|
-
// ===================
|
|
1807
|
-
/**
|
|
1808
|
-
* Get private balance information
|
|
1809
|
-
*/
|
|
1810
1685
|
async getPrivateBalanceInfo() {
|
|
1811
1686
|
this.ensureConnected();
|
|
1812
1687
|
try {
|
|
@@ -1822,7 +1697,7 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1822
1697
|
*/
|
|
1823
1698
|
async encryptBalance(amount) {
|
|
1824
1699
|
this.ensureConnected();
|
|
1825
|
-
|
|
1700
|
+
this.assertExactOCTAmount(amount, 'Encrypt amount');
|
|
1826
1701
|
if (!isValidAmount(amount)) {
|
|
1827
1702
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Invalid amount');
|
|
1828
1703
|
}
|
|
@@ -1843,7 +1718,7 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1843
1718
|
*/
|
|
1844
1719
|
async decryptBalance(amount) {
|
|
1845
1720
|
this.ensureConnected();
|
|
1846
|
-
|
|
1721
|
+
this.assertExactOCTAmount(amount, 'Decrypt amount');
|
|
1847
1722
|
if (!isValidAmount(amount)) {
|
|
1848
1723
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Invalid amount');
|
|
1849
1724
|
}
|
|
@@ -1869,21 +1744,21 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1869
1744
|
*/
|
|
1870
1745
|
async sendPrivateTransfer(transferData) {
|
|
1871
1746
|
this.ensureConnected();
|
|
1872
|
-
// validate inputs
|
|
1873
1747
|
if (!isValidAddress(transferData.to)) {
|
|
1874
1748
|
throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_ADDRESS, 'Invalid recipient address');
|
|
1875
1749
|
}
|
|
1876
1750
|
if (!isValidAmount(transferData.amount)) {
|
|
1877
1751
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Invalid transfer amount');
|
|
1878
1752
|
}
|
|
1753
|
+
this.assertExactOCTAmount(transferData.amount, 'Transfer amount');
|
|
1879
1754
|
// bound msg size
|
|
1880
1755
|
if (transferData.message && transferData.message.length > 1000) {
|
|
1881
1756
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Transfer message too long (max 1,000 characters)');
|
|
1882
1757
|
}
|
|
1883
1758
|
try {
|
|
1884
1759
|
const result = await this.communicator.sendRequest('send_private_transfer', transferData);
|
|
1885
|
-
// Refresh balance after transfer
|
|
1886
|
-
if (result.success) {
|
|
1760
|
+
// Refresh balance after transfer (accept RFC 'accepted' or legacy 'success')
|
|
1761
|
+
if (result.accepted ?? result.success) {
|
|
1887
1762
|
setTimeout(() => {
|
|
1888
1763
|
this.getBalance(true).catch(() => { });
|
|
1889
1764
|
}, 1000);
|
|
@@ -1923,8 +1798,8 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1923
1798
|
const result = await this.communicator.sendRequest('claim_private_transfer', {
|
|
1924
1799
|
transferId
|
|
1925
1800
|
});
|
|
1926
|
-
// Refresh balance after claiming
|
|
1927
|
-
if (result.success) {
|
|
1801
|
+
// Refresh balance after claiming (accept RFC 'accepted' or legacy 'success')
|
|
1802
|
+
if (result.accepted ?? result.success) {
|
|
1928
1803
|
setTimeout(() => {
|
|
1929
1804
|
this.getBalance(true).catch(() => { });
|
|
1930
1805
|
}, 1000);
|
|
@@ -1935,9 +1810,6 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1935
1810
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Failed to claim private transfer', error);
|
|
1936
1811
|
}
|
|
1937
1812
|
}
|
|
1938
|
-
// ===================
|
|
1939
|
-
// MESSAGE SIGNING
|
|
1940
|
-
// ===================
|
|
1941
1813
|
/**
|
|
1942
1814
|
* Sign an arbitrary message with the wallet's private key
|
|
1943
1815
|
* The user will be prompted to approve the signature request in the extension
|
|
@@ -1975,9 +1847,6 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1975
1847
|
throw new ZeroXIOWalletError(exports.ErrorCode.SIGNATURE_FAILED, 'Failed to sign message', error);
|
|
1976
1848
|
}
|
|
1977
1849
|
}
|
|
1978
|
-
// ===================
|
|
1979
|
-
// AUTHENTICATION HELPERS
|
|
1980
|
-
// ===================
|
|
1981
1850
|
/**
|
|
1982
1851
|
* Sign a domain-separated authentication message.
|
|
1983
1852
|
* Unlike `signMessage()`, this prepends a standard header that binds the signature
|
|
@@ -1999,9 +1868,6 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
1999
1868
|
const domainSeparated = `0xio auth\nService: ${service}\nNonce: ${nonce}\nOrigin: ${origin}`;
|
|
2000
1869
|
return this.signMessage(domainSeparated);
|
|
2001
1870
|
}
|
|
2002
|
-
// ===================
|
|
2003
|
-
// PRIVATE METHODS
|
|
2004
|
-
// ===================
|
|
2005
1871
|
ensureInitialized() {
|
|
2006
1872
|
if (!this.isInitialized) {
|
|
2007
1873
|
throw new ZeroXIOWalletError(exports.ErrorCode.UNKNOWN_ERROR, 'SDK not initialized. Call initialize() first.');
|
|
@@ -2014,7 +1880,6 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
2014
1880
|
}
|
|
2015
1881
|
}
|
|
2016
1882
|
setupExtensionEventListeners() {
|
|
2017
|
-
// Listen for extension events through the communicator
|
|
2018
1883
|
this.communicator.on('accountChanged', (event) => {
|
|
2019
1884
|
this.handleAccountChanged(event.data);
|
|
2020
1885
|
});
|
|
@@ -2033,11 +1898,18 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
2033
1898
|
this.communicator.on('transactionConfirmed', (event) => {
|
|
2034
1899
|
this.handleTransactionConfirmed(event.data);
|
|
2035
1900
|
});
|
|
1901
|
+
this.communicator.on('permissionsChanged', (event) => {
|
|
1902
|
+
const permissions = event.data ?? event;
|
|
1903
|
+
if (this.connectionInfo.isConnected) {
|
|
1904
|
+
this.connectionInfo.permissions = Array.isArray(permissions) ? permissions : [];
|
|
1905
|
+
}
|
|
1906
|
+
this.emit('permissionsChanged', permissions);
|
|
1907
|
+
});
|
|
1908
|
+
this.communicator.on('message', (event) => {
|
|
1909
|
+
this.emit('message', event.data ?? event);
|
|
1910
|
+
});
|
|
2036
1911
|
this.logger.log('Extension event listeners setup complete');
|
|
2037
1912
|
}
|
|
2038
|
-
/**
|
|
2039
|
-
* Handle account changed event from extension
|
|
2040
|
-
*/
|
|
2041
1913
|
handleAccountChanged(data) {
|
|
2042
1914
|
++this._sessionVersion;
|
|
2043
1915
|
const previousAddress = this.connectionInfo.address;
|
|
@@ -2045,7 +1917,6 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
2045
1917
|
// clear stale pubkey on acct change
|
|
2046
1918
|
this.connectionInfo.publicKey = data.publicKey;
|
|
2047
1919
|
if (data.balance) {
|
|
2048
|
-
// validate balance before caching
|
|
2049
1920
|
const validated = validateBalance(data.balance);
|
|
2050
1921
|
if (validated) {
|
|
2051
1922
|
this.connectionInfo.balance = validated;
|
|
@@ -2063,9 +1934,6 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
2063
1934
|
this.emit('accountChanged', accountChangedEvent);
|
|
2064
1935
|
this.logger.log('Account changed:', { newAddress: accountChangedEvent.newAddress });
|
|
2065
1936
|
}
|
|
2066
|
-
/**
|
|
2067
|
-
* Handle network changed event from extension
|
|
2068
|
-
*/
|
|
2069
1937
|
handleNetworkChanged(data) {
|
|
2070
1938
|
const previousNetwork = this.connectionInfo.networkInfo;
|
|
2071
1939
|
// validate networkInfo — drop invalid
|
|
@@ -2084,11 +1952,7 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
2084
1952
|
this.emit('networkChanged', networkChangedEvent);
|
|
2085
1953
|
this.logger.log('Network changed:', networkChangedEvent);
|
|
2086
1954
|
}
|
|
2087
|
-
/**
|
|
2088
|
-
* Handle balance changed event from extension
|
|
2089
|
-
*/
|
|
2090
1955
|
handleBalanceChanged(data) {
|
|
2091
|
-
// validate balance before caching
|
|
2092
1956
|
const balance = validateBalance(data.balance);
|
|
2093
1957
|
if (!balance) {
|
|
2094
1958
|
this.logger.warn('Received invalid balance in balanceChanged event, ignoring');
|
|
@@ -2104,13 +1968,9 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
2104
1968
|
this.emit('balanceChanged', balanceChangedEvent);
|
|
2105
1969
|
this.logger.log('Balance changed:', { public: balance.public });
|
|
2106
1970
|
}
|
|
2107
|
-
/**
|
|
2108
|
-
* Handle extension locked event
|
|
2109
|
-
*/
|
|
2110
1971
|
handleExtensionLocked() {
|
|
2111
1972
|
++this._sessionVersion;
|
|
2112
1973
|
this.connectionInfo = { isConnected: false };
|
|
2113
|
-
// emit extensionLocked then disconnect
|
|
2114
1974
|
this.emit('extensionLocked', {});
|
|
2115
1975
|
const disconnectEvent = {
|
|
2116
1976
|
reason: 'extension_locked'
|
|
@@ -2118,21 +1978,13 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
2118
1978
|
this.emit('disconnect', disconnectEvent);
|
|
2119
1979
|
this.logger.log('Extension locked - disconnected');
|
|
2120
1980
|
}
|
|
2121
|
-
/**
|
|
2122
|
-
* Handle extension unlocked event
|
|
2123
|
-
*/
|
|
2124
1981
|
handleExtensionUnlocked() {
|
|
2125
|
-
// emit extensionUnlocked
|
|
2126
1982
|
this.emit('extensionUnlocked', {});
|
|
2127
|
-
// Attempt to restore connection
|
|
2128
1983
|
this.getConnectionStatus().catch(() => {
|
|
2129
1984
|
this.logger.warn('Could not restore connection after unlock');
|
|
2130
1985
|
});
|
|
2131
1986
|
this.logger.log('Extension unlocked');
|
|
2132
1987
|
}
|
|
2133
|
-
/**
|
|
2134
|
-
* Handle transaction confirmed event
|
|
2135
|
-
*/
|
|
2136
1988
|
handleTransactionConfirmed(data) {
|
|
2137
1989
|
this.emit('transactionConfirmed', {
|
|
2138
1990
|
txHash: data.txHash,
|
|
@@ -2145,12 +1997,21 @@ class ZeroXIOWallet extends EventEmitter {
|
|
|
2145
1997
|
}, 2000);
|
|
2146
1998
|
this.logger.log('Transaction confirmed:', data.txHash);
|
|
2147
1999
|
}
|
|
2148
|
-
// ===================
|
|
2149
|
-
// CLEANUP
|
|
2150
|
-
// ===================
|
|
2151
2000
|
/**
|
|
2152
|
-
*
|
|
2001
|
+
* Reject numeric amounts that cannot be represented exactly in micro-OCT.
|
|
2002
|
+
* e.g. 0.1 + 0.2 = 0.30000000000000004 — the extension would sign the wrong value.
|
|
2003
|
+
* String amounts bypass this check (caller is responsible for correctness).
|
|
2153
2004
|
*/
|
|
2005
|
+
assertExactOCTAmount(amount, label) {
|
|
2006
|
+
if (typeof amount === 'number') {
|
|
2007
|
+
const micro = Math.round(amount * 1000000);
|
|
2008
|
+
if (Math.abs(amount - micro / 1000000) > 1e-10) {
|
|
2009
|
+
const suggested = (micro / 1000000).toFixed(6);
|
|
2010
|
+
throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_AMOUNT, `${label} cannot be represented exactly in micro-OCT. ` +
|
|
2011
|
+
`Pass a string instead (e.g. "${suggested}").`);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2154
2015
|
cleanup() {
|
|
2155
2016
|
this.communicator.cleanup();
|
|
2156
2017
|
this.removeAllListeners();
|
|
@@ -2187,7 +2048,9 @@ const SDK_TO_RFC = {
|
|
|
2187
2048
|
switch_network: 'octra_switchNetwork',
|
|
2188
2049
|
signMessage: 'octra_signMessage',
|
|
2189
2050
|
send_transaction: 'octra_sendTransaction',
|
|
2190
|
-
|
|
2051
|
+
sign_transaction: 'octra_signTransaction',
|
|
2052
|
+
broadcast_only: 'octra_submitTransaction',
|
|
2053
|
+
call_contract: 'octra_sendContractTransaction',
|
|
2191
2054
|
contract_call_view: 'octra_callContract',
|
|
2192
2055
|
get_private_balance_info: 'octra_getEncryptedBalance',
|
|
2193
2056
|
encrypt_balance: 'octra_encryptBalance',
|
|
@@ -2199,7 +2062,7 @@ const SDK_TO_RFC = {
|
|
|
2199
2062
|
const RFC_TO_SDK_ERROR = {
|
|
2200
2063
|
4001: 'USER_REJECTED',
|
|
2201
2064
|
4100: 'PERMISSION_DENIED',
|
|
2202
|
-
4200: '
|
|
2065
|
+
4200: 'UNKNOWN_ERROR',
|
|
2203
2066
|
4900: 'CONNECTION_REFUSED',
|
|
2204
2067
|
4901: 'NETWORK_ERROR',
|
|
2205
2068
|
};
|
|
@@ -2216,7 +2079,7 @@ function mapError(err) {
|
|
|
2216
2079
|
* three RFC-O-1 calls: octra_requestAccounts, octra_networkInfo, octra_permissions.
|
|
2217
2080
|
*/
|
|
2218
2081
|
async function rfcConnect(provider, params) {
|
|
2219
|
-
const requestPerms = params?.requestPermissions ?? [];
|
|
2082
|
+
const requestPerms = params?.permissions ?? params?.requestPermissions ?? [];
|
|
2220
2083
|
const accounts = (await provider.request({
|
|
2221
2084
|
method: 'octra_requestAccounts',
|
|
2222
2085
|
params: [{ permissions: requestPerms }],
|
|
@@ -2304,12 +2167,14 @@ function createOctraProviderAdapter() {
|
|
|
2304
2167
|
const onNetworkChanged = (data) => handler({ eventType: 'networkChanged', eventData: { networkInfo: data } });
|
|
2305
2168
|
const onBalanceChanged = (data) => handler({ eventType: 'balanceChanged', eventData: data });
|
|
2306
2169
|
const onTransactionChanged = (data) => handler({ eventType: 'transactionConfirmed', eventData: data });
|
|
2170
|
+
const onPermissionsChanged = (data) => handler({ eventType: 'permissionsChanged', eventData: data });
|
|
2307
2171
|
provider.on('connect', onConnect);
|
|
2308
2172
|
provider.on('disconnect', onDisconnect);
|
|
2309
2173
|
provider.on('accountsChanged', onAccountsChanged);
|
|
2310
2174
|
provider.on('networkChanged', onNetworkChanged);
|
|
2311
2175
|
provider.on('balanceChanged', onBalanceChanged);
|
|
2312
2176
|
provider.on('transactionChanged', onTransactionChanged);
|
|
2177
|
+
provider.on('permissionsChanged', onPermissionsChanged);
|
|
2313
2178
|
const cleanup = () => {
|
|
2314
2179
|
provider.removeListener('connect', onConnect);
|
|
2315
2180
|
provider.removeListener('disconnect', onDisconnect);
|
|
@@ -2317,6 +2182,7 @@ function createOctraProviderAdapter() {
|
|
|
2317
2182
|
provider.removeListener('networkChanged', onNetworkChanged);
|
|
2318
2183
|
provider.removeListener('balanceChanged', onBalanceChanged);
|
|
2319
2184
|
provider.removeListener('transactionChanged', onTransactionChanged);
|
|
2185
|
+
provider.removeListener('permissionsChanged', onPermissionsChanged);
|
|
2320
2186
|
_handler = null;
|
|
2321
2187
|
};
|
|
2322
2188
|
return cleanup;
|
|
@@ -2324,7 +2190,11 @@ function createOctraProviderAdapter() {
|
|
|
2324
2190
|
listenForReady(onReady) {
|
|
2325
2191
|
const handler = () => onReady();
|
|
2326
2192
|
window.addEventListener('octraWalletReady', handler);
|
|
2327
|
-
|
|
2193
|
+
window.addEventListener('octra#initialized', handler);
|
|
2194
|
+
return () => {
|
|
2195
|
+
window.removeEventListener('octraWalletReady', handler);
|
|
2196
|
+
window.removeEventListener('octra#initialized', handler);
|
|
2197
|
+
};
|
|
2328
2198
|
},
|
|
2329
2199
|
};
|
|
2330
2200
|
}
|
|
@@ -2387,7 +2257,7 @@ function getAllAdapters() {
|
|
|
2387
2257
|
*/
|
|
2388
2258
|
// Main exports
|
|
2389
2259
|
// Version information
|
|
2390
|
-
const SDK_VERSION = '2.7.
|
|
2260
|
+
const SDK_VERSION = '2.7.1';
|
|
2391
2261
|
const MIN_EXTENSION_VERSION = '2.0.1'; // Mainnet Alpha
|
|
2392
2262
|
const MIN_EXTENSION_VERSION_DEVNET = '2.2.1'; // Devnet (contract calls, privacy)
|
|
2393
2263
|
const SUPPORTED_EXTENSION_VERSIONS = '^2.0.1'; // Supports all versions >= 2.0.1
|
|
@@ -2405,8 +2275,7 @@ async function createZeroXIOWallet(config) {
|
|
|
2405
2275
|
try {
|
|
2406
2276
|
await wallet$1.connect();
|
|
2407
2277
|
}
|
|
2408
|
-
catch
|
|
2409
|
-
if (config.debug) ;
|
|
2278
|
+
catch {
|
|
2410
2279
|
// Don't throw - let the app handle connection manually
|
|
2411
2280
|
}
|
|
2412
2281
|
}
|