@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.umd.js
CHANGED
|
@@ -4,28 +4,16 @@
|
|
|
4
4
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ZeroXIOWalletSDK = {}));
|
|
5
5
|
})(this, (function (exports) { 'use strict';
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* 0xio Wallet SDK - Event System
|
|
9
|
-
* Type-safe event emitter for wallet events
|
|
10
|
-
*/
|
|
11
7
|
class EventEmitter {
|
|
12
|
-
constructor(
|
|
8
|
+
constructor(_debug = false) {
|
|
13
9
|
this.listeners = new Map();
|
|
14
|
-
this.debug = debug;
|
|
15
10
|
}
|
|
16
|
-
/**
|
|
17
|
-
* Add event listener
|
|
18
|
-
*/
|
|
19
11
|
on(eventType, listener) {
|
|
20
12
|
if (!this.listeners.has(eventType)) {
|
|
21
13
|
this.listeners.set(eventType, new Set());
|
|
22
14
|
}
|
|
23
15
|
this.listeners.get(eventType).add(listener);
|
|
24
|
-
if (this.debug) ;
|
|
25
16
|
}
|
|
26
|
-
/**
|
|
27
|
-
* Remove event listener
|
|
28
|
-
*/
|
|
29
17
|
off(eventType, listener) {
|
|
30
18
|
const eventListeners = this.listeners.get(eventType);
|
|
31
19
|
if (eventListeners) {
|
|
@@ -33,12 +21,8 @@
|
|
|
33
21
|
if (eventListeners.size === 0) {
|
|
34
22
|
this.listeners.delete(eventType);
|
|
35
23
|
}
|
|
36
|
-
if (this.debug) ;
|
|
37
24
|
}
|
|
38
25
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Add one-time event listener
|
|
41
|
-
*/
|
|
42
26
|
once(eventType, listener) {
|
|
43
27
|
const onceListener = (event) => {
|
|
44
28
|
// Remove BEFORE calling so a throwing listener doesn't stay registered
|
|
@@ -47,9 +31,6 @@
|
|
|
47
31
|
};
|
|
48
32
|
this.on(eventType, onceListener);
|
|
49
33
|
}
|
|
50
|
-
/**
|
|
51
|
-
* Emit event to all listeners
|
|
52
|
-
*/
|
|
53
34
|
emit(eventType, data) {
|
|
54
35
|
const event = {
|
|
55
36
|
type: eventType,
|
|
@@ -58,49 +39,32 @@
|
|
|
58
39
|
};
|
|
59
40
|
const eventListeners = this.listeners.get(eventType);
|
|
60
41
|
if (eventListeners && eventListeners.size > 0) {
|
|
61
|
-
if
|
|
62
|
-
|
|
63
|
-
const listenersArray = Array.from(eventListeners);
|
|
64
|
-
for (const listener of listenersArray) {
|
|
42
|
+
// snapshot to avoid issues if listeners modify the set during iteration
|
|
43
|
+
for (const listener of Array.from(eventListeners)) {
|
|
65
44
|
try {
|
|
66
45
|
listener(event);
|
|
67
46
|
}
|
|
68
|
-
catch
|
|
69
|
-
//
|
|
47
|
+
catch {
|
|
48
|
+
// listener errors are swallowed to keep the event loop running
|
|
70
49
|
}
|
|
71
50
|
}
|
|
72
51
|
}
|
|
73
|
-
else if (this.debug) ;
|
|
74
52
|
}
|
|
75
|
-
/**
|
|
76
|
-
* Remove all listeners for a specific event type
|
|
77
|
-
*/
|
|
78
53
|
removeAllListeners(eventType) {
|
|
79
54
|
if (eventType) {
|
|
80
55
|
this.listeners.delete(eventType);
|
|
81
|
-
if (this.debug) ;
|
|
82
56
|
}
|
|
83
57
|
else {
|
|
84
58
|
this.listeners.clear();
|
|
85
|
-
if (this.debug) ;
|
|
86
59
|
}
|
|
87
60
|
}
|
|
88
|
-
/**
|
|
89
|
-
* Get number of listeners for an event type
|
|
90
|
-
*/
|
|
91
61
|
listenerCount(eventType) {
|
|
92
62
|
const eventListeners = this.listeners.get(eventType);
|
|
93
63
|
return eventListeners ? eventListeners.size : 0;
|
|
94
64
|
}
|
|
95
|
-
/**
|
|
96
|
-
* Get all event types that have listeners
|
|
97
|
-
*/
|
|
98
65
|
eventTypes() {
|
|
99
66
|
return Array.from(this.listeners.keys());
|
|
100
67
|
}
|
|
101
|
-
/**
|
|
102
|
-
* Check if there are any listeners for an event type
|
|
103
|
-
*/
|
|
104
68
|
hasListeners(eventType) {
|
|
105
69
|
return this.listenerCount(eventType) > 0;
|
|
106
70
|
}
|
|
@@ -273,16 +237,6 @@
|
|
|
273
237
|
/** Default 0xio adapter instance (no extra trusted origins). */
|
|
274
238
|
const ZeroXIOAdapter = createZeroXIOAdapter();
|
|
275
239
|
|
|
276
|
-
/**
|
|
277
|
-
* 0xio Wallet SDK - Utilities
|
|
278
|
-
* Helper functions for validation, formatting, and common operations
|
|
279
|
-
*/
|
|
280
|
-
// ===================
|
|
281
|
-
// VALIDATION UTILITIES
|
|
282
|
-
// ===================
|
|
283
|
-
/**
|
|
284
|
-
* Validate wallet address for Octra blockchain
|
|
285
|
-
*/
|
|
286
240
|
function isValidAddress(address) {
|
|
287
241
|
if (!address || typeof address !== 'string') {
|
|
288
242
|
return false;
|
|
@@ -306,9 +260,6 @@
|
|
|
306
260
|
Number.isFinite(amount) &&
|
|
307
261
|
amount <= Number.MAX_SAFE_INTEGER;
|
|
308
262
|
}
|
|
309
|
-
/**
|
|
310
|
-
* Validate transaction message
|
|
311
|
-
*/
|
|
312
263
|
function isValidMessage(message) {
|
|
313
264
|
// Type check first — falsy non-strings (0, false, null) are NOT valid messages
|
|
314
265
|
if (typeof message !== 'string') {
|
|
@@ -321,15 +272,9 @@
|
|
|
321
272
|
// 100KB limit — contract call params can be large (serialized JSON)
|
|
322
273
|
return message.length <= 100000;
|
|
323
274
|
}
|
|
324
|
-
/**
|
|
325
|
-
* Validate fee level
|
|
326
|
-
*/
|
|
327
275
|
function isValidFeeLevel(feeLevel) {
|
|
328
276
|
return feeLevel === 1 || feeLevel === 3;
|
|
329
277
|
}
|
|
330
|
-
// ===================
|
|
331
|
-
// ADDRESS DERIVATION
|
|
332
|
-
// ===================
|
|
333
278
|
const _B58_ALPHA = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
334
279
|
function _base58Encode(buf) {
|
|
335
280
|
let zeros = 0;
|
|
@@ -374,12 +319,6 @@
|
|
|
374
319
|
const hashBuf = await crypto.subtle.digest('SHA-256', bytes);
|
|
375
320
|
return 'oct' + _base58Encode(new Uint8Array(hashBuf));
|
|
376
321
|
}
|
|
377
|
-
// ===================
|
|
378
|
-
// FORMATTING UTILITIES
|
|
379
|
-
// ===================
|
|
380
|
-
/**
|
|
381
|
-
* Format OCT amount for display
|
|
382
|
-
*/
|
|
383
322
|
function formatOCT(amount, decimals = 6) {
|
|
384
323
|
const n = typeof amount === 'string' ? parseFloat(amount) : amount;
|
|
385
324
|
if (!isValidAmount(n)) {
|
|
@@ -390,9 +329,6 @@
|
|
|
390
329
|
maximumFractionDigits: decimals
|
|
391
330
|
});
|
|
392
331
|
}
|
|
393
|
-
/**
|
|
394
|
-
* Format address for display (truncated)
|
|
395
|
-
*/
|
|
396
332
|
function formatAddress(address, prefixLength = 6, suffixLength = 4) {
|
|
397
333
|
if (!isValidAddress(address)) {
|
|
398
334
|
return 'Invalid Address';
|
|
@@ -402,16 +338,10 @@
|
|
|
402
338
|
}
|
|
403
339
|
return `${address.slice(0, prefixLength)}...${address.slice(-suffixLength)}`;
|
|
404
340
|
}
|
|
405
|
-
/**
|
|
406
|
-
* Format timestamp for display
|
|
407
|
-
*/
|
|
408
341
|
function formatTimestamp(timestamp) {
|
|
409
342
|
const date = new Date(timestamp);
|
|
410
343
|
return date.toLocaleString();
|
|
411
344
|
}
|
|
412
|
-
/**
|
|
413
|
-
* Format transaction hash for display
|
|
414
|
-
*/
|
|
415
345
|
function formatTxHash(hash, length = 12) {
|
|
416
346
|
if (!hash || typeof hash !== 'string') {
|
|
417
347
|
return 'Invalid Hash';
|
|
@@ -423,12 +353,6 @@
|
|
|
423
353
|
const suffixLength = Math.floor(length / 2);
|
|
424
354
|
return `${hash.slice(0, prefixLength)}...${hash.slice(-suffixLength)}`;
|
|
425
355
|
}
|
|
426
|
-
// ===================
|
|
427
|
-
// CONVERSION UTILITIES
|
|
428
|
-
// ===================
|
|
429
|
-
/**
|
|
430
|
-
* Convert OCT to micro OCT (for network transmission)
|
|
431
|
-
*/
|
|
432
356
|
function toMicroOCT(amount) {
|
|
433
357
|
if (!isValidAmount(amount)) {
|
|
434
358
|
throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_AMOUNT, 'Invalid amount for conversion');
|
|
@@ -437,9 +361,6 @@
|
|
|
437
361
|
const microOCT = Math.round(amount * 1000000);
|
|
438
362
|
return microOCT.toString();
|
|
439
363
|
}
|
|
440
|
-
/**
|
|
441
|
-
* Convert micro OCT to OCT (for display)
|
|
442
|
-
*/
|
|
443
364
|
function fromMicroOCT(microAmount) {
|
|
444
365
|
const amount = typeof microAmount === 'string' ? parseInt(microAmount, 10) : microAmount;
|
|
445
366
|
if (!Number.isFinite(amount) || amount < 0) {
|
|
@@ -447,12 +368,6 @@
|
|
|
447
368
|
}
|
|
448
369
|
return amount / 1000000;
|
|
449
370
|
}
|
|
450
|
-
// ===================
|
|
451
|
-
// ERROR UTILITIES
|
|
452
|
-
// ===================
|
|
453
|
-
/**
|
|
454
|
-
* Create standardized error messages
|
|
455
|
-
*/
|
|
456
371
|
function createErrorMessage(code, context) {
|
|
457
372
|
const baseMessages = {
|
|
458
373
|
[exports.ErrorCode.EXTENSION_NOT_FOUND]: '0xio Wallet extension is not installed or enabled',
|
|
@@ -479,24 +394,12 @@
|
|
|
479
394
|
const baseMessage = baseMessages[code] || 'Unknown error';
|
|
480
395
|
return context ? `${baseMessage}: ${context}` : baseMessage;
|
|
481
396
|
}
|
|
482
|
-
/**
|
|
483
|
-
* Check if error is a specific type
|
|
484
|
-
*/
|
|
485
397
|
function isErrorType(error, code) {
|
|
486
398
|
return error instanceof ZeroXIOWalletError && error.code === code;
|
|
487
399
|
}
|
|
488
|
-
// ===================
|
|
489
|
-
// ASYNC UTILITIES
|
|
490
|
-
// ===================
|
|
491
|
-
/**
|
|
492
|
-
* Create a promise that resolves after a delay
|
|
493
|
-
*/
|
|
494
400
|
function delay(ms) {
|
|
495
401
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
496
402
|
}
|
|
497
|
-
/**
|
|
498
|
-
* Retry an async operation with exponential backoff
|
|
499
|
-
*/
|
|
500
403
|
async function retry(operation, maxRetries = 3, baseDelay = 1000) {
|
|
501
404
|
let lastError;
|
|
502
405
|
// maxRetries = number of retries AFTER the first attempt
|
|
@@ -536,18 +439,9 @@
|
|
|
536
439
|
clearTimeout(timer);
|
|
537
440
|
});
|
|
538
441
|
}
|
|
539
|
-
// ===================
|
|
540
|
-
// BROWSER UTILITIES
|
|
541
|
-
// ===================
|
|
542
|
-
/**
|
|
543
|
-
* Check if running in browser environment
|
|
544
|
-
*/
|
|
545
442
|
function isBrowser() {
|
|
546
443
|
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
547
444
|
}
|
|
548
|
-
/**
|
|
549
|
-
* Check if browser supports required features
|
|
550
|
-
*/
|
|
551
445
|
function checkBrowserSupport() {
|
|
552
446
|
const missingFeatures = [];
|
|
553
447
|
if (!isBrowser()) {
|
|
@@ -569,12 +463,6 @@
|
|
|
569
463
|
missingFeatures
|
|
570
464
|
};
|
|
571
465
|
}
|
|
572
|
-
// ===================
|
|
573
|
-
// DEVELOPMENT UTILITIES
|
|
574
|
-
// ===================
|
|
575
|
-
/**
|
|
576
|
-
* Generate mock data for development/testing
|
|
577
|
-
*/
|
|
578
466
|
function generateMockData() {
|
|
579
467
|
return {
|
|
580
468
|
address: 'oct' + Math.random().toString(36).substring(2, 22) + Math.random().toString(36).substring(2, 26),
|
|
@@ -597,9 +485,6 @@
|
|
|
597
485
|
}
|
|
598
486
|
};
|
|
599
487
|
}
|
|
600
|
-
/**
|
|
601
|
-
* Create development logger
|
|
602
|
-
*/
|
|
603
488
|
function createLogger(prefix, debug) {
|
|
604
489
|
const isDevelopment = typeof window !== 'undefined' && ((typeof globalThis !== 'undefined' && globalThis.process?.env?.NODE_ENV === 'development') ||
|
|
605
490
|
window.location.hostname === 'localhost' ||
|
|
@@ -652,17 +537,6 @@
|
|
|
652
537
|
};
|
|
653
538
|
}
|
|
654
539
|
|
|
655
|
-
/**
|
|
656
|
-
* 0xio Wallet SDK - Extension Communication Module
|
|
657
|
-
*
|
|
658
|
-
* @fileoverview Manages secure communication between the SDK and browser extension.
|
|
659
|
-
* Implements message passing, request/response handling, rate limiting, and origin validation
|
|
660
|
-
* to ensure secure wallet interactions.
|
|
661
|
-
*
|
|
662
|
-
* @module communication
|
|
663
|
-
* @version 2.7.0
|
|
664
|
-
* @license MIT
|
|
665
|
-
*/
|
|
666
540
|
class ExtensionCommunicator extends EventEmitter {
|
|
667
541
|
constructor(debug = false, trustedOrigins = [], adapter) {
|
|
668
542
|
super(debug);
|
|
@@ -913,9 +787,12 @@
|
|
|
913
787
|
return;
|
|
914
788
|
}
|
|
915
789
|
const isSameOrigin = event.origin === window.location.origin;
|
|
916
|
-
const isLocalhost = event.origin.startsWith('http://localhost:') || event.origin.startsWith('http://127.0.0.1:');
|
|
917
790
|
const isTauri = event.origin === 'tauri://localhost' || event.origin === 'https://tauri.localhost';
|
|
918
|
-
const
|
|
791
|
+
const hasExplicitList = this.trustedOrigins.length > 0;
|
|
792
|
+
// When trustedParentOrigins is explicitly set, implicit localhost trust is disabled
|
|
793
|
+
const isLocalhostAllowed = !hasExplicitList &&
|
|
794
|
+
(event.origin.startsWith('http://localhost:') || event.origin.startsWith('http://127.0.0.1:'));
|
|
795
|
+
const isTrustedOrigin = this.trustedOrigins.includes(event.origin) || isTauri || isLocalhostAllowed;
|
|
919
796
|
// In iframe mode, only trust the actual parent window
|
|
920
797
|
const inIframe = window.parent !== window;
|
|
921
798
|
if (inIframe && event.source !== window.parent) {
|
|
@@ -1076,6 +953,7 @@
|
|
|
1076
953
|
// causing double popups where the second tx fails (stale nonce/state).
|
|
1077
954
|
ExtensionCommunicator.NO_RETRY_METHODS = new Set([
|
|
1078
955
|
'connect', 'send_transaction', 'call_contract', 'signMessage',
|
|
956
|
+
'sign_transaction', 'broadcast_only',
|
|
1079
957
|
'send_private_transfer', 'claim_private_transfer',
|
|
1080
958
|
'encrypt_balance', 'decrypt_balance',
|
|
1081
959
|
]);
|
|
@@ -1083,8 +961,8 @@
|
|
|
1083
961
|
// only forward known event types
|
|
1084
962
|
ExtensionCommunicator.VALID_EVENT_TYPES = new Set([
|
|
1085
963
|
'connect', 'disconnect', 'accountChanged', 'balanceChanged',
|
|
1086
|
-
'networkChanged', 'transactionConfirmed', '
|
|
1087
|
-
'extensionLocked', 'extensionUnlocked'
|
|
964
|
+
'networkChanged', 'transactionConfirmed', 'permissionsChanged', 'message',
|
|
965
|
+
'error', 'extensionLocked', 'extensionUnlocked'
|
|
1088
966
|
]);
|
|
1089
967
|
|
|
1090
968
|
/**
|
|
@@ -1167,6 +1045,14 @@
|
|
|
1167
1045
|
return null;
|
|
1168
1046
|
if (typeof raw.rpcUrl !== 'string' || (!raw.rpcUrl && raw.id !== 'custom'))
|
|
1169
1047
|
return null;
|
|
1048
|
+
if (raw.rpcUrl) {
|
|
1049
|
+
const isHttps = raw.rpcUrl.startsWith('https://');
|
|
1050
|
+
const isLocal = raw.rpcUrl.startsWith('http://localhost') || raw.rpcUrl.startsWith('http://127.0.0.1');
|
|
1051
|
+
const isTestnet = raw.isTestnet === true;
|
|
1052
|
+
// Reject plain-http RPC URLs from untrusted sources unless testnet-flagged or local
|
|
1053
|
+
if (!isHttps && !isLocal && !isTestnet)
|
|
1054
|
+
return null;
|
|
1055
|
+
}
|
|
1170
1056
|
if (typeof raw.supportsPrivacy !== 'boolean')
|
|
1171
1057
|
return null;
|
|
1172
1058
|
return Object.freeze({
|
|
@@ -1182,9 +1068,6 @@
|
|
|
1182
1068
|
});
|
|
1183
1069
|
}
|
|
1184
1070
|
|
|
1185
|
-
/**
|
|
1186
|
-
* SDK Configuration
|
|
1187
|
-
*/
|
|
1188
1071
|
/**
|
|
1189
1072
|
* Default balance structure.
|
|
1190
1073
|
* Accepts a numeric total or undefined — never pass a Balance object here.
|
|
@@ -1220,27 +1103,17 @@
|
|
|
1220
1103
|
currency: 'OCT'
|
|
1221
1104
|
};
|
|
1222
1105
|
}
|
|
1223
|
-
/**
|
|
1224
|
-
* SDK Configuration constants
|
|
1225
|
-
*/
|
|
1226
1106
|
const SDK_CONFIG = {
|
|
1227
|
-
version: '2.7.
|
|
1107
|
+
version: '2.7.1',
|
|
1228
1108
|
defaultNetworkId: DEFAULT_NETWORK_ID,
|
|
1229
1109
|
communicationTimeout: 30000, // 30 seconds
|
|
1230
1110
|
retryAttempts: 3,
|
|
1231
1111
|
retryDelay: 1000, // 1 second
|
|
1232
1112
|
};
|
|
1233
|
-
/**
|
|
1234
|
-
* Get default network configuration
|
|
1235
|
-
*/
|
|
1236
1113
|
function getDefaultNetwork() {
|
|
1237
1114
|
return getNetworkConfig(SDK_CONFIG.defaultNetworkId);
|
|
1238
1115
|
}
|
|
1239
1116
|
|
|
1240
|
-
/**
|
|
1241
|
-
* 0xio Wallet SDK - Main Wallet Class
|
|
1242
|
-
* Primary interface for DApp developers to interact with 0xio Wallet
|
|
1243
|
-
*/
|
|
1244
1117
|
class ZeroXIOWallet extends EventEmitter {
|
|
1245
1118
|
constructor(config) {
|
|
1246
1119
|
super(config.debug);
|
|
@@ -1256,32 +1129,22 @@
|
|
|
1256
1129
|
debug: config.debug || false
|
|
1257
1130
|
};
|
|
1258
1131
|
this.logger = createLogger('ZeroXIOWallet', this.config.debug || false);
|
|
1259
|
-
this.communicator = new ExtensionCommunicator(this.config.debug, [], this.config.adapter);
|
|
1132
|
+
this.communicator = new ExtensionCommunicator(this.config.debug, this.config.trustedParentOrigins ?? [], this.config.adapter);
|
|
1260
1133
|
this.logger.log('Wallet instance created with config:', this.config);
|
|
1261
1134
|
}
|
|
1262
|
-
// ===================
|
|
1263
|
-
// INITIALIZATION
|
|
1264
|
-
// ===================
|
|
1265
|
-
/**
|
|
1266
|
-
* Initialize the SDK
|
|
1267
|
-
* Must be called before using any other methods
|
|
1268
|
-
*/
|
|
1269
1135
|
async initialize() {
|
|
1270
1136
|
if (this.isInitialized) {
|
|
1271
1137
|
return true;
|
|
1272
1138
|
}
|
|
1273
|
-
// single-flight init
|
|
1274
1139
|
if (this._initPromise) {
|
|
1275
1140
|
return this._initPromise;
|
|
1276
1141
|
}
|
|
1277
1142
|
this._initPromise = (async () => {
|
|
1278
1143
|
try {
|
|
1279
|
-
// Initialize extension communication
|
|
1280
1144
|
const communicationReady = await this.communicator.initialize();
|
|
1281
1145
|
if (!communicationReady) {
|
|
1282
1146
|
throw new ZeroXIOWalletError(exports.ErrorCode.EXTENSION_NOT_FOUND, 'Failed to establish communication with 0xio Wallet extension');
|
|
1283
1147
|
}
|
|
1284
|
-
// Register this DApp with the extension
|
|
1285
1148
|
await this.communicator.sendRequest('register_dapp', {
|
|
1286
1149
|
appName: this.config.appName,
|
|
1287
1150
|
appDescription: this.config.appDescription,
|
|
@@ -1291,7 +1154,6 @@
|
|
|
1291
1154
|
requiredPermissions: this.config.requiredPermissions,
|
|
1292
1155
|
networkId: this.config.networkId
|
|
1293
1156
|
});
|
|
1294
|
-
// Setup event forwarding from extension
|
|
1295
1157
|
this.setupExtensionEventListeners();
|
|
1296
1158
|
this.isInitialized = true;
|
|
1297
1159
|
this.logger.log('SDK initialized successfully');
|
|
@@ -1310,29 +1172,21 @@
|
|
|
1310
1172
|
})();
|
|
1311
1173
|
return this._initPromise;
|
|
1312
1174
|
}
|
|
1313
|
-
/**
|
|
1314
|
-
* Check if SDK is initialized
|
|
1315
|
-
*/
|
|
1316
1175
|
isReady() {
|
|
1317
1176
|
return this.isInitialized && this.communicator.isExtensionAvailable();
|
|
1318
1177
|
}
|
|
1319
|
-
// ===================
|
|
1320
|
-
// CONNECTION MANAGEMENT
|
|
1321
|
-
// ===================
|
|
1322
|
-
/**
|
|
1323
|
-
* Connect to wallet
|
|
1324
|
-
*/
|
|
1325
1178
|
async connect(options = {}) {
|
|
1326
1179
|
this.ensureInitialized();
|
|
1327
1180
|
try {
|
|
1328
1181
|
this.logger.log('Attempting to connect with options:', options);
|
|
1329
|
-
// filter to declared perms only
|
|
1182
|
+
// filter to declared perms only — accept both RFC 'permissions' and legacy 'requestPermissions'
|
|
1330
1183
|
const declaredPermissions = this.config.requiredPermissions || [];
|
|
1331
|
-
const
|
|
1332
|
-
|
|
1184
|
+
const requestedPerms = options.permissions ?? options.requestPermissions;
|
|
1185
|
+
const requestedPermissions = requestedPerms
|
|
1186
|
+
? requestedPerms.filter(p => declaredPermissions.includes(p))
|
|
1333
1187
|
: declaredPermissions;
|
|
1334
1188
|
const result = await this.communicator.sendRequest('connect', {
|
|
1335
|
-
|
|
1189
|
+
permissions: requestedPermissions,
|
|
1336
1190
|
networkId: options.networkId || this.config.networkId
|
|
1337
1191
|
});
|
|
1338
1192
|
// verify pubkey→addr binding
|
|
@@ -1349,9 +1203,12 @@
|
|
|
1349
1203
|
this.logger.warn('Address derivation check skipped (crypto unavailable):', e);
|
|
1350
1204
|
}
|
|
1351
1205
|
}
|
|
1352
|
-
// Use networkInfo from extension response — validate before caching
|
|
1206
|
+
// Use networkInfo from extension response — validate before caching.
|
|
1353
1207
|
const networkInfo = validateNetworkInfo(result.networkInfo)
|
|
1354
|
-
??
|
|
1208
|
+
?? (result.networkId ? getNetworkConfig(result.networkId) : null);
|
|
1209
|
+
if (!networkInfo) {
|
|
1210
|
+
throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Wallet did not return valid network metadata.');
|
|
1211
|
+
}
|
|
1355
1212
|
const permissions = result.permissions || [];
|
|
1356
1213
|
// Update connection info — including permissions
|
|
1357
1214
|
this.connectionInfo = {
|
|
@@ -1444,7 +1301,11 @@
|
|
|
1444
1301
|
// validate untrusted balance/networkInfo before caching
|
|
1445
1302
|
const balanceInfo = validateBalance(result.balance) ?? createDefaultBalance();
|
|
1446
1303
|
const networkInfo = validateNetworkInfo(result.networkInfo)
|
|
1447
|
-
??
|
|
1304
|
+
?? (result.networkId ? getNetworkConfig(result.networkId) : null);
|
|
1305
|
+
if (!networkInfo) {
|
|
1306
|
+
this.logger.warn('getConnectionStatus: wallet returned no network metadata — returning cached state');
|
|
1307
|
+
return this.connectionInfo;
|
|
1308
|
+
}
|
|
1448
1309
|
const wasConnected = this.connectionInfo.isConnected;
|
|
1449
1310
|
const permissions = result.permissions || [];
|
|
1450
1311
|
// preserve existing connectedAt
|
|
@@ -1517,18 +1378,9 @@
|
|
|
1517
1378
|
getNetworkId() {
|
|
1518
1379
|
return this.connectionInfo.networkInfo?.id || null;
|
|
1519
1380
|
}
|
|
1520
|
-
// ===================
|
|
1521
|
-
// WALLET INFORMATION
|
|
1522
|
-
// ===================
|
|
1523
|
-
/**
|
|
1524
|
-
* Get current wallet address
|
|
1525
|
-
*/
|
|
1526
1381
|
getAddress() {
|
|
1527
1382
|
return this.connectionInfo.address || null;
|
|
1528
1383
|
}
|
|
1529
|
-
/**
|
|
1530
|
-
* Get current balance
|
|
1531
|
-
*/
|
|
1532
1384
|
async getBalance(forceRefresh = false) {
|
|
1533
1385
|
this.ensureConnected();
|
|
1534
1386
|
try {
|
|
@@ -1553,7 +1405,6 @@
|
|
|
1553
1405
|
// skip if session changed mid-flight
|
|
1554
1406
|
if (this._sessionVersion !== sv)
|
|
1555
1407
|
return result;
|
|
1556
|
-
// Update cached balance
|
|
1557
1408
|
if (this.connectionInfo.balance) {
|
|
1558
1409
|
const previousBalance = this.connectionInfo.balance;
|
|
1559
1410
|
this.connectionInfo.balance = result;
|
|
@@ -1580,15 +1431,11 @@
|
|
|
1580
1431
|
throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Failed to get balance', error);
|
|
1581
1432
|
}
|
|
1582
1433
|
}
|
|
1583
|
-
/**
|
|
1584
|
-
* Get network information
|
|
1585
|
-
*/
|
|
1586
1434
|
async getNetworkInfo() {
|
|
1587
1435
|
this.ensureInitialized();
|
|
1588
1436
|
try {
|
|
1589
1437
|
const sv = this._sessionVersion;
|
|
1590
1438
|
const result = await this.communicator.sendRequest('get_network_info');
|
|
1591
|
-
// validate network info before caching
|
|
1592
1439
|
const networkInfo = validateNetworkInfo(result);
|
|
1593
1440
|
if (!networkInfo) {
|
|
1594
1441
|
throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Extension returned invalid network info');
|
|
@@ -1596,11 +1443,9 @@
|
|
|
1596
1443
|
// skip if session changed mid-flight
|
|
1597
1444
|
if (this._sessionVersion !== sv)
|
|
1598
1445
|
return networkInfo;
|
|
1599
|
-
// Update cached network info
|
|
1600
1446
|
if (this.connectionInfo.networkInfo) {
|
|
1601
1447
|
const previousNetwork = this.connectionInfo.networkInfo;
|
|
1602
1448
|
this.connectionInfo.networkInfo = networkInfo;
|
|
1603
|
-
// Emit network changed event if different
|
|
1604
1449
|
if (previousNetwork.id !== networkInfo.id) {
|
|
1605
1450
|
const networkChangedEvent = {
|
|
1606
1451
|
previousNetwork,
|
|
@@ -1620,22 +1465,14 @@
|
|
|
1620
1465
|
throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Failed to get network info', error);
|
|
1621
1466
|
}
|
|
1622
1467
|
}
|
|
1623
|
-
// ===================
|
|
1624
|
-
// TRANSACTIONS
|
|
1625
|
-
// ===================
|
|
1626
|
-
/**
|
|
1627
|
-
* Send transaction
|
|
1628
|
-
*/
|
|
1629
1468
|
async sendTransaction(txData) {
|
|
1630
1469
|
this.ensureConnected();
|
|
1631
|
-
// validate inputs
|
|
1632
1470
|
if (!isValidAddress(txData.to)) {
|
|
1633
1471
|
throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_ADDRESS, 'Invalid recipient address');
|
|
1634
1472
|
}
|
|
1635
1473
|
if (!isValidAmount(txData.amount)) {
|
|
1636
1474
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Invalid transaction amount');
|
|
1637
1475
|
}
|
|
1638
|
-
// bound memo
|
|
1639
1476
|
if (txData.message && txData.message.length > 1000) {
|
|
1640
1477
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Transaction message too long (max 1,000 characters)');
|
|
1641
1478
|
}
|
|
@@ -1644,8 +1481,8 @@
|
|
|
1644
1481
|
this.logger.log('Sending transaction:', { to: txData.to });
|
|
1645
1482
|
const result = await this.communicator.sendRequest('send_transaction', txData);
|
|
1646
1483
|
this.logger.log('Transaction result:', result);
|
|
1647
|
-
// Refresh balance after successful transaction
|
|
1648
|
-
if (result.success) {
|
|
1484
|
+
// Refresh balance after successful transaction (accept RFC 'accepted' or legacy 'success')
|
|
1485
|
+
if (result.accepted ?? result.success) {
|
|
1649
1486
|
setTimeout(() => {
|
|
1650
1487
|
this.getBalance(true).catch(error => {
|
|
1651
1488
|
this.logger.warn('Failed to refresh balance after transaction:', error);
|
|
@@ -1662,23 +1499,70 @@
|
|
|
1662
1499
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Failed to send transaction', error);
|
|
1663
1500
|
}
|
|
1664
1501
|
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Sign a transaction without broadcasting it (RFC-O-1 octra_signTransaction).
|
|
1504
|
+
* Returns the signed transaction object for manual submission via submitTransaction().
|
|
1505
|
+
*/
|
|
1506
|
+
async signTransaction(txData) {
|
|
1507
|
+
this.ensureConnected();
|
|
1508
|
+
if (!isValidAddress(txData.to)) {
|
|
1509
|
+
throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_ADDRESS, 'Invalid recipient address');
|
|
1510
|
+
}
|
|
1511
|
+
if (!isValidAmount(txData.amount)) {
|
|
1512
|
+
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Invalid transaction amount');
|
|
1513
|
+
}
|
|
1514
|
+
if (txData.message && txData.message.length > 1000) {
|
|
1515
|
+
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Transaction message too long (max 1,000 characters)');
|
|
1516
|
+
}
|
|
1517
|
+
try {
|
|
1518
|
+
this.logger.log('Requesting transaction signature:', { to: txData.to });
|
|
1519
|
+
const result = await this.communicator.sendRequest('sign_transaction', txData);
|
|
1520
|
+
return result;
|
|
1521
|
+
}
|
|
1522
|
+
catch (error) {
|
|
1523
|
+
if (error instanceof ZeroXIOWalletError)
|
|
1524
|
+
throw error;
|
|
1525
|
+
throw new ZeroXIOWalletError(exports.ErrorCode.SIGNATURE_FAILED, 'Failed to sign transaction', error);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
/**
|
|
1529
|
+
* Broadcast a pre-signed transaction (RFC-O-1 octra_submitTransaction).
|
|
1530
|
+
* Use after signTransaction() to submit the signed tx to the network.
|
|
1531
|
+
*/
|
|
1532
|
+
async submitTransaction(signedTx) {
|
|
1533
|
+
this.ensureConnected();
|
|
1534
|
+
if (!signedTx || typeof signedTx !== 'object') {
|
|
1535
|
+
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'signedTx must be an object');
|
|
1536
|
+
}
|
|
1537
|
+
try {
|
|
1538
|
+
this.logger.log('Submitting pre-signed transaction');
|
|
1539
|
+
const result = await this.communicator.sendRequest('broadcast_only', { signedTx });
|
|
1540
|
+
return result;
|
|
1541
|
+
}
|
|
1542
|
+
catch (error) {
|
|
1543
|
+
if (error instanceof ZeroXIOWalletError)
|
|
1544
|
+
throw error;
|
|
1545
|
+
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Failed to submit transaction', error);
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1665
1548
|
/**
|
|
1666
1549
|
* Call a smart contract method (state-changing).
|
|
1667
1550
|
* The extension builds, signs, and submits the transaction via octra_submit.
|
|
1668
1551
|
*/
|
|
1669
1552
|
async callContract(callData) {
|
|
1670
1553
|
this.ensureConnected();
|
|
1671
|
-
// validate inputs
|
|
1672
1554
|
if (!isValidAddress(callData.contract)) {
|
|
1673
1555
|
throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_ADDRESS, 'Invalid contract address');
|
|
1674
1556
|
}
|
|
1675
1557
|
if (!callData.method || typeof callData.method !== 'string') {
|
|
1676
1558
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Contract method is required');
|
|
1677
1559
|
}
|
|
1678
|
-
// bound method + params size
|
|
1679
1560
|
if (callData.method.length > 200) {
|
|
1680
1561
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Contract method name too long (max 200 characters)');
|
|
1681
1562
|
}
|
|
1563
|
+
if (callData.amount != null) {
|
|
1564
|
+
this.assertExactOCTAmount(callData.amount, 'Contract call amount');
|
|
1565
|
+
}
|
|
1682
1566
|
try {
|
|
1683
1567
|
if (JSON.stringify(callData.params).length > 65536) {
|
|
1684
1568
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Contract params too large (max 64 KB)');
|
|
@@ -1716,14 +1600,12 @@
|
|
|
1716
1600
|
*/
|
|
1717
1601
|
async contractCallView(viewData) {
|
|
1718
1602
|
this.ensureInitialized();
|
|
1719
|
-
// validate inputs
|
|
1720
1603
|
if (!isValidAddress(viewData.contract)) {
|
|
1721
1604
|
throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_ADDRESS, 'Invalid contract address');
|
|
1722
1605
|
}
|
|
1723
1606
|
if (!viewData.method || typeof viewData.method !== 'string') {
|
|
1724
1607
|
throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Contract method is required');
|
|
1725
1608
|
}
|
|
1726
|
-
// bound method + params size
|
|
1727
1609
|
if (viewData.method.length > 200) {
|
|
1728
1610
|
throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Contract method name too long (max 200 characters)');
|
|
1729
1611
|
}
|
|
@@ -1763,7 +1645,6 @@
|
|
|
1763
1645
|
*/
|
|
1764
1646
|
async getContractStorage(contract, key) {
|
|
1765
1647
|
this.ensureInitialized();
|
|
1766
|
-
// validate inputs
|
|
1767
1648
|
if (!isValidAddress(contract)) {
|
|
1768
1649
|
throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_ADDRESS, 'Invalid contract address');
|
|
1769
1650
|
}
|
|
@@ -1805,12 +1686,6 @@
|
|
|
1805
1686
|
throw new ZeroXIOWalletError(exports.ErrorCode.NETWORK_ERROR, 'Failed to get transaction history', error);
|
|
1806
1687
|
}
|
|
1807
1688
|
}
|
|
1808
|
-
// ===================
|
|
1809
|
-
// PRIVATE FEATURES
|
|
1810
|
-
// ===================
|
|
1811
|
-
/**
|
|
1812
|
-
* Get private balance information
|
|
1813
|
-
*/
|
|
1814
1689
|
async getPrivateBalanceInfo() {
|
|
1815
1690
|
this.ensureConnected();
|
|
1816
1691
|
try {
|
|
@@ -1826,7 +1701,7 @@
|
|
|
1826
1701
|
*/
|
|
1827
1702
|
async encryptBalance(amount) {
|
|
1828
1703
|
this.ensureConnected();
|
|
1829
|
-
|
|
1704
|
+
this.assertExactOCTAmount(amount, 'Encrypt amount');
|
|
1830
1705
|
if (!isValidAmount(amount)) {
|
|
1831
1706
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Invalid amount');
|
|
1832
1707
|
}
|
|
@@ -1847,7 +1722,7 @@
|
|
|
1847
1722
|
*/
|
|
1848
1723
|
async decryptBalance(amount) {
|
|
1849
1724
|
this.ensureConnected();
|
|
1850
|
-
|
|
1725
|
+
this.assertExactOCTAmount(amount, 'Decrypt amount');
|
|
1851
1726
|
if (!isValidAmount(amount)) {
|
|
1852
1727
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Invalid amount');
|
|
1853
1728
|
}
|
|
@@ -1873,21 +1748,21 @@
|
|
|
1873
1748
|
*/
|
|
1874
1749
|
async sendPrivateTransfer(transferData) {
|
|
1875
1750
|
this.ensureConnected();
|
|
1876
|
-
// validate inputs
|
|
1877
1751
|
if (!isValidAddress(transferData.to)) {
|
|
1878
1752
|
throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_ADDRESS, 'Invalid recipient address');
|
|
1879
1753
|
}
|
|
1880
1754
|
if (!isValidAmount(transferData.amount)) {
|
|
1881
1755
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Invalid transfer amount');
|
|
1882
1756
|
}
|
|
1757
|
+
this.assertExactOCTAmount(transferData.amount, 'Transfer amount');
|
|
1883
1758
|
// bound msg size
|
|
1884
1759
|
if (transferData.message && transferData.message.length > 1000) {
|
|
1885
1760
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Transfer message too long (max 1,000 characters)');
|
|
1886
1761
|
}
|
|
1887
1762
|
try {
|
|
1888
1763
|
const result = await this.communicator.sendRequest('send_private_transfer', transferData);
|
|
1889
|
-
// Refresh balance after transfer
|
|
1890
|
-
if (result.success) {
|
|
1764
|
+
// Refresh balance after transfer (accept RFC 'accepted' or legacy 'success')
|
|
1765
|
+
if (result.accepted ?? result.success) {
|
|
1891
1766
|
setTimeout(() => {
|
|
1892
1767
|
this.getBalance(true).catch(() => { });
|
|
1893
1768
|
}, 1000);
|
|
@@ -1927,8 +1802,8 @@
|
|
|
1927
1802
|
const result = await this.communicator.sendRequest('claim_private_transfer', {
|
|
1928
1803
|
transferId
|
|
1929
1804
|
});
|
|
1930
|
-
// Refresh balance after claiming
|
|
1931
|
-
if (result.success) {
|
|
1805
|
+
// Refresh balance after claiming (accept RFC 'accepted' or legacy 'success')
|
|
1806
|
+
if (result.accepted ?? result.success) {
|
|
1932
1807
|
setTimeout(() => {
|
|
1933
1808
|
this.getBalance(true).catch(() => { });
|
|
1934
1809
|
}, 1000);
|
|
@@ -1939,9 +1814,6 @@
|
|
|
1939
1814
|
throw new ZeroXIOWalletError(exports.ErrorCode.TRANSACTION_FAILED, 'Failed to claim private transfer', error);
|
|
1940
1815
|
}
|
|
1941
1816
|
}
|
|
1942
|
-
// ===================
|
|
1943
|
-
// MESSAGE SIGNING
|
|
1944
|
-
// ===================
|
|
1945
1817
|
/**
|
|
1946
1818
|
* Sign an arbitrary message with the wallet's private key
|
|
1947
1819
|
* The user will be prompted to approve the signature request in the extension
|
|
@@ -1979,9 +1851,6 @@
|
|
|
1979
1851
|
throw new ZeroXIOWalletError(exports.ErrorCode.SIGNATURE_FAILED, 'Failed to sign message', error);
|
|
1980
1852
|
}
|
|
1981
1853
|
}
|
|
1982
|
-
// ===================
|
|
1983
|
-
// AUTHENTICATION HELPERS
|
|
1984
|
-
// ===================
|
|
1985
1854
|
/**
|
|
1986
1855
|
* Sign a domain-separated authentication message.
|
|
1987
1856
|
* Unlike `signMessage()`, this prepends a standard header that binds the signature
|
|
@@ -2003,9 +1872,6 @@
|
|
|
2003
1872
|
const domainSeparated = `0xio auth\nService: ${service}\nNonce: ${nonce}\nOrigin: ${origin}`;
|
|
2004
1873
|
return this.signMessage(domainSeparated);
|
|
2005
1874
|
}
|
|
2006
|
-
// ===================
|
|
2007
|
-
// PRIVATE METHODS
|
|
2008
|
-
// ===================
|
|
2009
1875
|
ensureInitialized() {
|
|
2010
1876
|
if (!this.isInitialized) {
|
|
2011
1877
|
throw new ZeroXIOWalletError(exports.ErrorCode.UNKNOWN_ERROR, 'SDK not initialized. Call initialize() first.');
|
|
@@ -2018,7 +1884,6 @@
|
|
|
2018
1884
|
}
|
|
2019
1885
|
}
|
|
2020
1886
|
setupExtensionEventListeners() {
|
|
2021
|
-
// Listen for extension events through the communicator
|
|
2022
1887
|
this.communicator.on('accountChanged', (event) => {
|
|
2023
1888
|
this.handleAccountChanged(event.data);
|
|
2024
1889
|
});
|
|
@@ -2037,11 +1902,18 @@
|
|
|
2037
1902
|
this.communicator.on('transactionConfirmed', (event) => {
|
|
2038
1903
|
this.handleTransactionConfirmed(event.data);
|
|
2039
1904
|
});
|
|
1905
|
+
this.communicator.on('permissionsChanged', (event) => {
|
|
1906
|
+
const permissions = event.data ?? event;
|
|
1907
|
+
if (this.connectionInfo.isConnected) {
|
|
1908
|
+
this.connectionInfo.permissions = Array.isArray(permissions) ? permissions : [];
|
|
1909
|
+
}
|
|
1910
|
+
this.emit('permissionsChanged', permissions);
|
|
1911
|
+
});
|
|
1912
|
+
this.communicator.on('message', (event) => {
|
|
1913
|
+
this.emit('message', event.data ?? event);
|
|
1914
|
+
});
|
|
2040
1915
|
this.logger.log('Extension event listeners setup complete');
|
|
2041
1916
|
}
|
|
2042
|
-
/**
|
|
2043
|
-
* Handle account changed event from extension
|
|
2044
|
-
*/
|
|
2045
1917
|
handleAccountChanged(data) {
|
|
2046
1918
|
++this._sessionVersion;
|
|
2047
1919
|
const previousAddress = this.connectionInfo.address;
|
|
@@ -2049,7 +1921,6 @@
|
|
|
2049
1921
|
// clear stale pubkey on acct change
|
|
2050
1922
|
this.connectionInfo.publicKey = data.publicKey;
|
|
2051
1923
|
if (data.balance) {
|
|
2052
|
-
// validate balance before caching
|
|
2053
1924
|
const validated = validateBalance(data.balance);
|
|
2054
1925
|
if (validated) {
|
|
2055
1926
|
this.connectionInfo.balance = validated;
|
|
@@ -2067,9 +1938,6 @@
|
|
|
2067
1938
|
this.emit('accountChanged', accountChangedEvent);
|
|
2068
1939
|
this.logger.log('Account changed:', { newAddress: accountChangedEvent.newAddress });
|
|
2069
1940
|
}
|
|
2070
|
-
/**
|
|
2071
|
-
* Handle network changed event from extension
|
|
2072
|
-
*/
|
|
2073
1941
|
handleNetworkChanged(data) {
|
|
2074
1942
|
const previousNetwork = this.connectionInfo.networkInfo;
|
|
2075
1943
|
// validate networkInfo — drop invalid
|
|
@@ -2088,11 +1956,7 @@
|
|
|
2088
1956
|
this.emit('networkChanged', networkChangedEvent);
|
|
2089
1957
|
this.logger.log('Network changed:', networkChangedEvent);
|
|
2090
1958
|
}
|
|
2091
|
-
/**
|
|
2092
|
-
* Handle balance changed event from extension
|
|
2093
|
-
*/
|
|
2094
1959
|
handleBalanceChanged(data) {
|
|
2095
|
-
// validate balance before caching
|
|
2096
1960
|
const balance = validateBalance(data.balance);
|
|
2097
1961
|
if (!balance) {
|
|
2098
1962
|
this.logger.warn('Received invalid balance in balanceChanged event, ignoring');
|
|
@@ -2108,13 +1972,9 @@
|
|
|
2108
1972
|
this.emit('balanceChanged', balanceChangedEvent);
|
|
2109
1973
|
this.logger.log('Balance changed:', { public: balance.public });
|
|
2110
1974
|
}
|
|
2111
|
-
/**
|
|
2112
|
-
* Handle extension locked event
|
|
2113
|
-
*/
|
|
2114
1975
|
handleExtensionLocked() {
|
|
2115
1976
|
++this._sessionVersion;
|
|
2116
1977
|
this.connectionInfo = { isConnected: false };
|
|
2117
|
-
// emit extensionLocked then disconnect
|
|
2118
1978
|
this.emit('extensionLocked', {});
|
|
2119
1979
|
const disconnectEvent = {
|
|
2120
1980
|
reason: 'extension_locked'
|
|
@@ -2122,21 +1982,13 @@
|
|
|
2122
1982
|
this.emit('disconnect', disconnectEvent);
|
|
2123
1983
|
this.logger.log('Extension locked - disconnected');
|
|
2124
1984
|
}
|
|
2125
|
-
/**
|
|
2126
|
-
* Handle extension unlocked event
|
|
2127
|
-
*/
|
|
2128
1985
|
handleExtensionUnlocked() {
|
|
2129
|
-
// emit extensionUnlocked
|
|
2130
1986
|
this.emit('extensionUnlocked', {});
|
|
2131
|
-
// Attempt to restore connection
|
|
2132
1987
|
this.getConnectionStatus().catch(() => {
|
|
2133
1988
|
this.logger.warn('Could not restore connection after unlock');
|
|
2134
1989
|
});
|
|
2135
1990
|
this.logger.log('Extension unlocked');
|
|
2136
1991
|
}
|
|
2137
|
-
/**
|
|
2138
|
-
* Handle transaction confirmed event
|
|
2139
|
-
*/
|
|
2140
1992
|
handleTransactionConfirmed(data) {
|
|
2141
1993
|
this.emit('transactionConfirmed', {
|
|
2142
1994
|
txHash: data.txHash,
|
|
@@ -2149,12 +2001,21 @@
|
|
|
2149
2001
|
}, 2000);
|
|
2150
2002
|
this.logger.log('Transaction confirmed:', data.txHash);
|
|
2151
2003
|
}
|
|
2152
|
-
// ===================
|
|
2153
|
-
// CLEANUP
|
|
2154
|
-
// ===================
|
|
2155
2004
|
/**
|
|
2156
|
-
*
|
|
2005
|
+
* Reject numeric amounts that cannot be represented exactly in micro-OCT.
|
|
2006
|
+
* e.g. 0.1 + 0.2 = 0.30000000000000004 — the extension would sign the wrong value.
|
|
2007
|
+
* String amounts bypass this check (caller is responsible for correctness).
|
|
2157
2008
|
*/
|
|
2009
|
+
assertExactOCTAmount(amount, label) {
|
|
2010
|
+
if (typeof amount === 'number') {
|
|
2011
|
+
const micro = Math.round(amount * 1000000);
|
|
2012
|
+
if (Math.abs(amount - micro / 1000000) > 1e-10) {
|
|
2013
|
+
const suggested = (micro / 1000000).toFixed(6);
|
|
2014
|
+
throw new ZeroXIOWalletError(exports.ErrorCode.INVALID_AMOUNT, `${label} cannot be represented exactly in micro-OCT. ` +
|
|
2015
|
+
`Pass a string instead (e.g. "${suggested}").`);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2158
2019
|
cleanup() {
|
|
2159
2020
|
this.communicator.cleanup();
|
|
2160
2021
|
this.removeAllListeners();
|
|
@@ -2191,7 +2052,9 @@
|
|
|
2191
2052
|
switch_network: 'octra_switchNetwork',
|
|
2192
2053
|
signMessage: 'octra_signMessage',
|
|
2193
2054
|
send_transaction: 'octra_sendTransaction',
|
|
2194
|
-
|
|
2055
|
+
sign_transaction: 'octra_signTransaction',
|
|
2056
|
+
broadcast_only: 'octra_submitTransaction',
|
|
2057
|
+
call_contract: 'octra_sendContractTransaction',
|
|
2195
2058
|
contract_call_view: 'octra_callContract',
|
|
2196
2059
|
get_private_balance_info: 'octra_getEncryptedBalance',
|
|
2197
2060
|
encrypt_balance: 'octra_encryptBalance',
|
|
@@ -2203,7 +2066,7 @@
|
|
|
2203
2066
|
const RFC_TO_SDK_ERROR = {
|
|
2204
2067
|
4001: 'USER_REJECTED',
|
|
2205
2068
|
4100: 'PERMISSION_DENIED',
|
|
2206
|
-
4200: '
|
|
2069
|
+
4200: 'UNKNOWN_ERROR',
|
|
2207
2070
|
4900: 'CONNECTION_REFUSED',
|
|
2208
2071
|
4901: 'NETWORK_ERROR',
|
|
2209
2072
|
};
|
|
@@ -2220,7 +2083,7 @@
|
|
|
2220
2083
|
* three RFC-O-1 calls: octra_requestAccounts, octra_networkInfo, octra_permissions.
|
|
2221
2084
|
*/
|
|
2222
2085
|
async function rfcConnect(provider, params) {
|
|
2223
|
-
const requestPerms = params?.requestPermissions ?? [];
|
|
2086
|
+
const requestPerms = params?.permissions ?? params?.requestPermissions ?? [];
|
|
2224
2087
|
const accounts = (await provider.request({
|
|
2225
2088
|
method: 'octra_requestAccounts',
|
|
2226
2089
|
params: [{ permissions: requestPerms }],
|
|
@@ -2308,12 +2171,14 @@
|
|
|
2308
2171
|
const onNetworkChanged = (data) => handler({ eventType: 'networkChanged', eventData: { networkInfo: data } });
|
|
2309
2172
|
const onBalanceChanged = (data) => handler({ eventType: 'balanceChanged', eventData: data });
|
|
2310
2173
|
const onTransactionChanged = (data) => handler({ eventType: 'transactionConfirmed', eventData: data });
|
|
2174
|
+
const onPermissionsChanged = (data) => handler({ eventType: 'permissionsChanged', eventData: data });
|
|
2311
2175
|
provider.on('connect', onConnect);
|
|
2312
2176
|
provider.on('disconnect', onDisconnect);
|
|
2313
2177
|
provider.on('accountsChanged', onAccountsChanged);
|
|
2314
2178
|
provider.on('networkChanged', onNetworkChanged);
|
|
2315
2179
|
provider.on('balanceChanged', onBalanceChanged);
|
|
2316
2180
|
provider.on('transactionChanged', onTransactionChanged);
|
|
2181
|
+
provider.on('permissionsChanged', onPermissionsChanged);
|
|
2317
2182
|
const cleanup = () => {
|
|
2318
2183
|
provider.removeListener('connect', onConnect);
|
|
2319
2184
|
provider.removeListener('disconnect', onDisconnect);
|
|
@@ -2321,6 +2186,7 @@
|
|
|
2321
2186
|
provider.removeListener('networkChanged', onNetworkChanged);
|
|
2322
2187
|
provider.removeListener('balanceChanged', onBalanceChanged);
|
|
2323
2188
|
provider.removeListener('transactionChanged', onTransactionChanged);
|
|
2189
|
+
provider.removeListener('permissionsChanged', onPermissionsChanged);
|
|
2324
2190
|
_handler = null;
|
|
2325
2191
|
};
|
|
2326
2192
|
return cleanup;
|
|
@@ -2328,7 +2194,11 @@
|
|
|
2328
2194
|
listenForReady(onReady) {
|
|
2329
2195
|
const handler = () => onReady();
|
|
2330
2196
|
window.addEventListener('octraWalletReady', handler);
|
|
2331
|
-
|
|
2197
|
+
window.addEventListener('octra#initialized', handler);
|
|
2198
|
+
return () => {
|
|
2199
|
+
window.removeEventListener('octraWalletReady', handler);
|
|
2200
|
+
window.removeEventListener('octra#initialized', handler);
|
|
2201
|
+
};
|
|
2332
2202
|
},
|
|
2333
2203
|
};
|
|
2334
2204
|
}
|
|
@@ -2391,7 +2261,7 @@
|
|
|
2391
2261
|
*/
|
|
2392
2262
|
// Main exports
|
|
2393
2263
|
// Version information
|
|
2394
|
-
const SDK_VERSION = '2.7.
|
|
2264
|
+
const SDK_VERSION = '2.7.1';
|
|
2395
2265
|
const MIN_EXTENSION_VERSION = '2.0.1'; // Mainnet Alpha
|
|
2396
2266
|
const MIN_EXTENSION_VERSION_DEVNET = '2.2.1'; // Devnet (contract calls, privacy)
|
|
2397
2267
|
const SUPPORTED_EXTENSION_VERSIONS = '^2.0.1'; // Supports all versions >= 2.0.1
|
|
@@ -2409,8 +2279,7 @@
|
|
|
2409
2279
|
try {
|
|
2410
2280
|
await wallet$1.connect();
|
|
2411
2281
|
}
|
|
2412
|
-
catch
|
|
2413
|
-
if (config.debug) ;
|
|
2282
|
+
catch {
|
|
2414
2283
|
// Don't throw - let the app handle connection manually
|
|
2415
2284
|
}
|
|
2416
2285
|
}
|