@sidhujag/sysweb3-keyring 1.0.544 → 1.0.547

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.
Files changed (212) hide show
  1. package/coverage/clover.xml +2875 -0
  2. package/coverage/coverage-final.json +29468 -0
  3. package/coverage/lcov-report/base.css +354 -0
  4. package/coverage/lcov-report/block-navigation.js +85 -0
  5. package/coverage/lcov-report/favicon.png +0 -0
  6. package/coverage/lcov-report/index.html +320 -0
  7. package/coverage/lcov-report/prettify.css +101 -0
  8. package/coverage/lcov-report/prettify.js +1008 -0
  9. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  10. package/coverage/lcov-report/sorter.js +191 -0
  11. package/coverage/lcov-report/src/index.html +276 -0
  12. package/coverage/lcov-report/src/index.ts.html +114 -0
  13. package/coverage/lcov-report/src/initial-state.ts.html +558 -0
  14. package/coverage/lcov-report/src/keyring-manager.ts.html +6279 -0
  15. package/coverage/lcov-report/src/ledger/bitcoin_client/index.html +178 -0
  16. package/coverage/lcov-report/src/ledger/bitcoin_client/index.ts.html +144 -0
  17. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/appClient.ts.html +1560 -0
  18. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/bip32.ts.html +276 -0
  19. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/buffertools.ts.html +495 -0
  20. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/clientCommands.ts.html +1138 -0
  21. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/index.html +363 -0
  22. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkelizedPsbt.ts.html +289 -0
  23. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkle.ts.html +486 -0
  24. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkleMap.ts.html +240 -0
  25. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/policy.ts.html +342 -0
  26. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/psbtv2.ts.html +2388 -0
  27. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/varint.ts.html +453 -0
  28. package/coverage/lcov-report/src/ledger/consts.ts.html +177 -0
  29. package/coverage/lcov-report/src/ledger/index.html +216 -0
  30. package/coverage/lcov-report/src/ledger/index.ts.html +1371 -0
  31. package/coverage/lcov-report/src/ledger/utils.ts.html +102 -0
  32. package/coverage/lcov-report/src/signers.ts.html +591 -0
  33. package/coverage/lcov-report/src/storage.ts.html +198 -0
  34. package/coverage/lcov-report/src/transactions/ethereum.ts.html +5826 -0
  35. package/coverage/lcov-report/src/transactions/index.html +216 -0
  36. package/coverage/lcov-report/src/transactions/index.ts.html +93 -0
  37. package/coverage/lcov-report/src/transactions/syscoin.ts.html +1521 -0
  38. package/coverage/lcov-report/src/trezor/index.html +176 -0
  39. package/coverage/lcov-report/src/trezor/index.ts.html +2655 -0
  40. package/coverage/lcov-report/src/types.ts.html +1443 -0
  41. package/coverage/lcov-report/src/utils/derivation-paths.ts.html +486 -0
  42. package/coverage/lcov-report/src/utils/index.html +196 -0
  43. package/coverage/lcov-report/src/utils/psbt.ts.html +159 -0
  44. package/coverage/lcov-report/test/helpers/constants.ts.html +627 -0
  45. package/coverage/lcov-report/test/helpers/index.html +176 -0
  46. package/coverage/lcov.info +4832 -0
  47. package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/appClient.js +1 -124
  48. package/dist/cjs/ledger/bitcoin_client/lib/appClient.js.map +1 -0
  49. package/{cjs → dist/cjs}/transactions/ethereum.js +24 -11
  50. package/dist/cjs/transactions/ethereum.js.map +1 -0
  51. package/dist/package.json +50 -0
  52. package/{types → dist/types}/ledger/bitcoin_client/lib/appClient.d.ts +0 -6
  53. package/examples/basic-usage.js +140 -0
  54. package/jest.config.js +32 -0
  55. package/package.json +31 -13
  56. package/readme.md +201 -0
  57. package/src/declare.d.ts +7 -0
  58. package/src/errorUtils.ts +83 -0
  59. package/src/hardware-wallet-manager.ts +655 -0
  60. package/src/index.ts +12 -0
  61. package/src/initial-state.ts +108 -0
  62. package/src/keyring-manager.ts +2698 -0
  63. package/src/ledger/bitcoin_client/index.ts +19 -0
  64. package/src/ledger/bitcoin_client/lib/appClient.ts +405 -0
  65. package/src/ledger/bitcoin_client/lib/bip32.ts +61 -0
  66. package/src/ledger/bitcoin_client/lib/buffertools.ts +134 -0
  67. package/src/ledger/bitcoin_client/lib/clientCommands.ts +356 -0
  68. package/src/ledger/bitcoin_client/lib/constants.ts +12 -0
  69. package/src/ledger/bitcoin_client/lib/merkelizedPsbt.ts +65 -0
  70. package/src/ledger/bitcoin_client/lib/merkle.ts +136 -0
  71. package/src/ledger/bitcoin_client/lib/merkleMap.ts +49 -0
  72. package/src/ledger/bitcoin_client/lib/policy.ts +91 -0
  73. package/src/ledger/bitcoin_client/lib/psbtv2.ts +768 -0
  74. package/src/ledger/bitcoin_client/lib/varint.ts +120 -0
  75. package/src/ledger/consts.ts +3 -0
  76. package/src/ledger/index.ts +685 -0
  77. package/src/ledger/types.ts +74 -0
  78. package/src/network-utils.ts +99 -0
  79. package/src/providers.ts +345 -0
  80. package/src/signers.ts +158 -0
  81. package/src/storage.ts +63 -0
  82. package/src/transactions/__tests__/integration.test.ts +303 -0
  83. package/src/transactions/__tests__/syscoin.test.ts +409 -0
  84. package/src/transactions/ethereum.ts +2503 -0
  85. package/src/transactions/index.ts +2 -0
  86. package/src/transactions/syscoin.ts +542 -0
  87. package/src/trezor/index.ts +1050 -0
  88. package/src/types.ts +366 -0
  89. package/src/utils/derivation-paths.ts +133 -0
  90. package/src/utils/psbt.ts +24 -0
  91. package/src/utils.ts +191 -0
  92. package/test/README.md +158 -0
  93. package/test/__mocks__/ledger-mock.js +20 -0
  94. package/test/__mocks__/trezor-mock.js +75 -0
  95. package/test/cleanup-summary.md +167 -0
  96. package/test/helpers/README.md +78 -0
  97. package/test/helpers/constants.ts +79 -0
  98. package/test/helpers/setup.ts +714 -0
  99. package/test/integration/import-validation.spec.ts +588 -0
  100. package/test/unit/hardware/ledger.spec.ts +869 -0
  101. package/test/unit/hardware/trezor.spec.ts +828 -0
  102. package/test/unit/keyring-manager/account-management.spec.ts +970 -0
  103. package/test/unit/keyring-manager/import-watchonly.spec.ts +181 -0
  104. package/test/unit/keyring-manager/import-wif.spec.ts +126 -0
  105. package/test/unit/keyring-manager/initialization.spec.ts +782 -0
  106. package/test/unit/keyring-manager/key-derivation.spec.ts +996 -0
  107. package/test/unit/keyring-manager/security.spec.ts +505 -0
  108. package/test/unit/keyring-manager/state-management.spec.ts +375 -0
  109. package/test/unit/network/network-management.spec.ts +372 -0
  110. package/test/unit/transactions/ethereum-transactions.spec.ts +382 -0
  111. package/test/unit/transactions/syscoin-transactions.spec.ts +615 -0
  112. package/tsconfig.json +14 -0
  113. package/cjs/ledger/bitcoin_client/lib/appClient.js.map +0 -1
  114. package/cjs/transactions/ethereum.js.map +0 -1
  115. /package/{README.md → dist/README.md} +0 -0
  116. /package/{cjs → dist/cjs}/errorUtils.js +0 -0
  117. /package/{cjs → dist/cjs}/errorUtils.js.map +0 -0
  118. /package/{cjs → dist/cjs}/hardware-wallet-manager.js +0 -0
  119. /package/{cjs → dist/cjs}/hardware-wallet-manager.js.map +0 -0
  120. /package/{cjs → dist/cjs}/index.js +0 -0
  121. /package/{cjs → dist/cjs}/index.js.map +0 -0
  122. /package/{cjs → dist/cjs}/initial-state.js +0 -0
  123. /package/{cjs → dist/cjs}/initial-state.js.map +0 -0
  124. /package/{cjs → dist/cjs}/keyring-manager.js +0 -0
  125. /package/{cjs → dist/cjs}/keyring-manager.js.map +0 -0
  126. /package/{cjs → dist/cjs}/ledger/bitcoin_client/index.js +0 -0
  127. /package/{cjs → dist/cjs}/ledger/bitcoin_client/index.js.map +0 -0
  128. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/bip32.js +0 -0
  129. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/bip32.js.map +0 -0
  130. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/buffertools.js +0 -0
  131. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/buffertools.js.map +0 -0
  132. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/clientCommands.js +0 -0
  133. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/clientCommands.js.map +0 -0
  134. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/constants.js +0 -0
  135. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/constants.js.map +0 -0
  136. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkelizedPsbt.js +0 -0
  137. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkelizedPsbt.js.map +0 -0
  138. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkle.js +0 -0
  139. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkle.js.map +0 -0
  140. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkleMap.js +0 -0
  141. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkleMap.js.map +0 -0
  142. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/policy.js +0 -0
  143. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/policy.js.map +0 -0
  144. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/psbtv2.js +0 -0
  145. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/psbtv2.js.map +0 -0
  146. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/varint.js +0 -0
  147. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/varint.js.map +0 -0
  148. /package/{cjs → dist/cjs}/ledger/consts.js +0 -0
  149. /package/{cjs → dist/cjs}/ledger/consts.js.map +0 -0
  150. /package/{cjs → dist/cjs}/ledger/index.js +0 -0
  151. /package/{cjs → dist/cjs}/ledger/index.js.map +0 -0
  152. /package/{cjs → dist/cjs}/ledger/types.js +0 -0
  153. /package/{cjs → dist/cjs}/ledger/types.js.map +0 -0
  154. /package/{cjs → dist/cjs}/network-utils.js +0 -0
  155. /package/{cjs → dist/cjs}/network-utils.js.map +0 -0
  156. /package/{cjs → dist/cjs}/providers.js +0 -0
  157. /package/{cjs → dist/cjs}/providers.js.map +0 -0
  158. /package/{cjs → dist/cjs}/signers.js +0 -0
  159. /package/{cjs → dist/cjs}/signers.js.map +0 -0
  160. /package/{cjs → dist/cjs}/storage.js +0 -0
  161. /package/{cjs → dist/cjs}/storage.js.map +0 -0
  162. /package/{cjs → dist/cjs}/transactions/__tests__/integration.test.js +0 -0
  163. /package/{cjs → dist/cjs}/transactions/__tests__/integration.test.js.map +0 -0
  164. /package/{cjs → dist/cjs}/transactions/__tests__/syscoin.test.js +0 -0
  165. /package/{cjs → dist/cjs}/transactions/__tests__/syscoin.test.js.map +0 -0
  166. /package/{cjs → dist/cjs}/transactions/index.js +0 -0
  167. /package/{cjs → dist/cjs}/transactions/index.js.map +0 -0
  168. /package/{cjs → dist/cjs}/transactions/syscoin.js +0 -0
  169. /package/{cjs → dist/cjs}/transactions/syscoin.js.map +0 -0
  170. /package/{cjs → dist/cjs}/trezor/index.js +0 -0
  171. /package/{cjs → dist/cjs}/trezor/index.js.map +0 -0
  172. /package/{cjs → dist/cjs}/types.js +0 -0
  173. /package/{cjs → dist/cjs}/types.js.map +0 -0
  174. /package/{cjs → dist/cjs}/utils/derivation-paths.js +0 -0
  175. /package/{cjs → dist/cjs}/utils/derivation-paths.js.map +0 -0
  176. /package/{cjs → dist/cjs}/utils/psbt.js +0 -0
  177. /package/{cjs → dist/cjs}/utils/psbt.js.map +0 -0
  178. /package/{cjs → dist/cjs}/utils.js +0 -0
  179. /package/{cjs → dist/cjs}/utils.js.map +0 -0
  180. /package/{types → dist/types}/errorUtils.d.ts +0 -0
  181. /package/{types → dist/types}/hardware-wallet-manager.d.ts +0 -0
  182. /package/{types → dist/types}/index.d.ts +0 -0
  183. /package/{types → dist/types}/initial-state.d.ts +0 -0
  184. /package/{types → dist/types}/keyring-manager.d.ts +0 -0
  185. /package/{types → dist/types}/ledger/bitcoin_client/index.d.ts +0 -0
  186. /package/{types → dist/types}/ledger/bitcoin_client/lib/bip32.d.ts +0 -0
  187. /package/{types → dist/types}/ledger/bitcoin_client/lib/buffertools.d.ts +0 -0
  188. /package/{types → dist/types}/ledger/bitcoin_client/lib/clientCommands.d.ts +0 -0
  189. /package/{types → dist/types}/ledger/bitcoin_client/lib/constants.d.ts +0 -0
  190. /package/{types → dist/types}/ledger/bitcoin_client/lib/merkelizedPsbt.d.ts +0 -0
  191. /package/{types → dist/types}/ledger/bitcoin_client/lib/merkle.d.ts +0 -0
  192. /package/{types → dist/types}/ledger/bitcoin_client/lib/merkleMap.d.ts +0 -0
  193. /package/{types → dist/types}/ledger/bitcoin_client/lib/policy.d.ts +0 -0
  194. /package/{types → dist/types}/ledger/bitcoin_client/lib/psbtv2.d.ts +0 -0
  195. /package/{types → dist/types}/ledger/bitcoin_client/lib/varint.d.ts +0 -0
  196. /package/{types → dist/types}/ledger/consts.d.ts +0 -0
  197. /package/{types → dist/types}/ledger/index.d.ts +0 -0
  198. /package/{types → dist/types}/ledger/types.d.ts +0 -0
  199. /package/{types → dist/types}/network-utils.d.ts +0 -0
  200. /package/{types → dist/types}/providers.d.ts +0 -0
  201. /package/{types → dist/types}/signers.d.ts +0 -0
  202. /package/{types → dist/types}/storage.d.ts +0 -0
  203. /package/{types → dist/types}/transactions/__tests__/integration.test.d.ts +0 -0
  204. /package/{types → dist/types}/transactions/__tests__/syscoin.test.d.ts +0 -0
  205. /package/{types → dist/types}/transactions/ethereum.d.ts +0 -0
  206. /package/{types → dist/types}/transactions/index.d.ts +0 -0
  207. /package/{types → dist/types}/transactions/syscoin.d.ts +0 -0
  208. /package/{types → dist/types}/trezor/index.d.ts +0 -0
  209. /package/{types → dist/types}/types.d.ts +0 -0
  210. /package/{types → dist/types}/utils/derivation-paths.d.ts +0 -0
  211. /package/{types → dist/types}/utils/psbt.d.ts +0 -0
  212. /package/{types → dist/types}/utils.d.ts +0 -0
@@ -0,0 +1,655 @@
1
+ import Transport from '@ledgerhq/hw-transport';
2
+ import HIDTransport from '@ledgerhq/hw-transport-webhid';
3
+ import { listen } from '@ledgerhq/logs';
4
+ import TrezorConnect from '@trezor/connect-webextension';
5
+ import { EventEmitter } from 'events';
6
+
7
+ export enum HardwareWalletType {
8
+ LEDGER = 'ledger',
9
+ TREZOR = 'trezor',
10
+ }
11
+
12
+ export enum ConnectionStatus {
13
+ DISCONNECTED = 'disconnected',
14
+ CONNECTING = 'connecting',
15
+ CONNECTED = 'connected',
16
+ ERROR = 'error',
17
+ }
18
+
19
+ interface ConnectionPoolEntry {
20
+ transport?: Transport | null;
21
+ status: ConnectionStatus;
22
+ lastActivity: number;
23
+ retryCount: number;
24
+ error?: Error;
25
+ }
26
+
27
+ interface RetryConfig {
28
+ maxRetries: number;
29
+ baseDelay: number;
30
+ maxDelay: number;
31
+ backoffMultiplier: number;
32
+ }
33
+
34
+ export interface HardwareWalletStatus {
35
+ type: HardwareWalletType;
36
+ status: ConnectionStatus;
37
+ connected: boolean;
38
+ lastSeen?: number;
39
+ error?: string;
40
+ deviceInfo?: {
41
+ model?: string;
42
+ firmwareVersion?: string;
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Hardware Wallet Manager with connection pooling, status monitoring, and error recovery
48
+ */
49
+ export class HardwareWalletManager extends EventEmitter {
50
+ private connectionPool: Map<string, ConnectionPoolEntry> = new Map();
51
+ private statusMonitorInterval: NodeJS.Timeout | null = null;
52
+ private readonly CONNECTION_TIMEOUT = 30000; // 30 seconds
53
+ private readonly IDLE_TIMEOUT = 5 * 60 * 1000; // 5 minutes
54
+ private readonly STATUS_CHECK_INTERVAL = 5000; // 5 seconds
55
+
56
+ private readonly retryConfig: RetryConfig = {
57
+ maxRetries: 3,
58
+ baseDelay: 1000, // 1 second
59
+ maxDelay: 10000, // 10 seconds
60
+ backoffMultiplier: 2,
61
+ };
62
+
63
+ constructor() {
64
+ super();
65
+ this.startStatusMonitoring();
66
+
67
+ // Set up Ledger debug logging
68
+ if (process.env.NODE_ENV === 'development') {
69
+ listen((log) => {
70
+ console.log(`[Ledger] ${log.type}: ${log.message}`);
71
+ });
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Get or create a Ledger connection with retry logic
77
+ */
78
+ async getLedgerConnection(): Promise<Transport> {
79
+ const key = `${HardwareWalletType.LEDGER}-default`;
80
+ const existing = this.connectionPool.get(key);
81
+
82
+ if (existing?.transport && existing.status === ConnectionStatus.CONNECTED) {
83
+ existing.lastActivity = Date.now();
84
+ return existing.transport;
85
+ }
86
+
87
+ return this.createLedgerConnectionWithRetry(key);
88
+ }
89
+
90
+ /**
91
+ * Create Ledger connection with exponential backoff retry
92
+ */
93
+ private async createLedgerConnectionWithRetry(
94
+ key: string
95
+ ): Promise<Transport> {
96
+ const entry: ConnectionPoolEntry = {
97
+ status: ConnectionStatus.CONNECTING,
98
+ lastActivity: Date.now(),
99
+ retryCount: 0,
100
+ };
101
+ this.connectionPool.set(key, entry);
102
+
103
+ let lastError: Error | undefined;
104
+
105
+ for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
106
+ try {
107
+ this.emit('connectionAttempt', {
108
+ type: HardwareWalletType.LEDGER,
109
+ attempt: attempt + 1,
110
+ maxAttempts: this.retryConfig.maxRetries + 1,
111
+ });
112
+
113
+ const transport = await this.createLedgerTransport();
114
+
115
+ entry.transport = transport;
116
+ entry.status = ConnectionStatus.CONNECTED;
117
+ entry.retryCount = 0;
118
+ entry.error = undefined;
119
+
120
+ this.emit('connected', { type: HardwareWalletType.LEDGER });
121
+
122
+ // Set up disconnect handler
123
+ transport.on('disconnect', () => {
124
+ this.handleDisconnect(key, HardwareWalletType.LEDGER);
125
+ });
126
+
127
+ return transport;
128
+ } catch (error) {
129
+ lastError = error as Error;
130
+ entry.retryCount = attempt + 1;
131
+ entry.error = lastError;
132
+
133
+ if (attempt < this.retryConfig.maxRetries) {
134
+ const delay = this.calculateBackoffDelay(attempt);
135
+
136
+ this.emit('retrying', {
137
+ type: HardwareWalletType.LEDGER,
138
+ attempt: attempt + 1,
139
+ nextRetryIn: delay,
140
+ error: lastError.message,
141
+ });
142
+
143
+ await this.delay(delay);
144
+ }
145
+ }
146
+ }
147
+
148
+ entry.status = ConnectionStatus.ERROR;
149
+ this.emit('connectionFailed', {
150
+ type: HardwareWalletType.LEDGER,
151
+ error: lastError?.message,
152
+ });
153
+
154
+ throw new Error(
155
+ `Failed to connect to Ledger after ${
156
+ this.retryConfig.maxRetries + 1
157
+ } attempts: ${lastError?.message || 'Unknown error'}`
158
+ );
159
+ }
160
+
161
+ /**
162
+ * Create Ledger transport with timeout
163
+ */
164
+ private async createLedgerTransport(): Promise<Transport> {
165
+ return Promise.race([
166
+ HIDTransport.create(),
167
+ this.createTimeoutPromise<Transport>(
168
+ this.CONNECTION_TIMEOUT,
169
+ 'Ledger connection timeout'
170
+ ),
171
+ ]);
172
+ }
173
+
174
+ /**
175
+ * Initialize Trezor with retry logic
176
+ */
177
+ async initializeTrezor(): Promise<boolean> {
178
+ const key = `${HardwareWalletType.TREZOR}-default`;
179
+ const existing = this.connectionPool.get(key);
180
+
181
+ if (existing?.status === ConnectionStatus.CONNECTED) {
182
+ existing.lastActivity = Date.now();
183
+ return true;
184
+ }
185
+
186
+ // Check if we're already trying to connect
187
+ if (existing?.status === ConnectionStatus.CONNECTING) {
188
+ console.log(
189
+ '[HardwareWalletManager] Trezor connection already in progress'
190
+ );
191
+ // Wait a bit and check again
192
+ await this.delay(1000);
193
+ const updated = this.connectionPool.get(key);
194
+ return updated?.status === ConnectionStatus.CONNECTED || false;
195
+ }
196
+
197
+ return this.initializeTrezorWithRetry(key);
198
+ }
199
+
200
+ /**
201
+ * Initialize Trezor with exponential backoff retry
202
+ */
203
+ private async initializeTrezorWithRetry(key: string): Promise<boolean> {
204
+ const entry: ConnectionPoolEntry = {
205
+ status: ConnectionStatus.CONNECTING,
206
+ lastActivity: Date.now(),
207
+ retryCount: 0,
208
+ };
209
+ this.connectionPool.set(key, entry);
210
+
211
+ let lastError: Error | undefined;
212
+
213
+ // Reduce retry attempts for Trezor to prevent repeated popups
214
+ const trezorRetryConfig = {
215
+ ...this.retryConfig,
216
+ maxRetries: 1, // Only retry once for Trezor
217
+ };
218
+
219
+ for (let attempt = 0; attempt <= trezorRetryConfig.maxRetries; attempt++) {
220
+ try {
221
+ this.emit('connectionAttempt', {
222
+ type: HardwareWalletType.TREZOR,
223
+ attempt: attempt + 1,
224
+ maxAttempts: trezorRetryConfig.maxRetries + 1,
225
+ });
226
+
227
+ // Dispose any existing iframe before initialization
228
+ try {
229
+ await TrezorConnect.dispose();
230
+ } catch (disposeError) {
231
+ console.log(
232
+ '[HardwareWalletManager] Dispose error (safe to ignore):',
233
+ disposeError
234
+ );
235
+ }
236
+
237
+ await TrezorConnect.init({
238
+ manifest: {
239
+ appUrl: 'https://paliwallet.com/',
240
+ email: 'support@syscoin.org',
241
+ },
242
+ lazyLoad: true,
243
+ popup: true,
244
+ connectSrc: 'https://connect.trezor.io/9/',
245
+ _extendWebextensionLifetime: true,
246
+ transports: ['BridgeTransport', 'WebUsbTransport'],
247
+ debug: false, // Disable debug mode to prevent extra logs
248
+ coreMode: 'popup', // Use popup mode for webUSB support in Chrome extension
249
+ });
250
+
251
+ entry.status = ConnectionStatus.CONNECTED;
252
+ entry.retryCount = 0;
253
+ entry.error = undefined;
254
+
255
+ this.emit('connected', { type: HardwareWalletType.TREZOR });
256
+
257
+ // Set up device event listeners
258
+ TrezorConnect.on('DEVICE_EVENT', (event: any) => {
259
+ if (event.type === 'device-disconnect') {
260
+ this.handleDisconnect(key, HardwareWalletType.TREZOR);
261
+ }
262
+ });
263
+
264
+ return true;
265
+ } catch (error) {
266
+ lastError = error as Error;
267
+
268
+ // Check if already initialized - this is actually OK
269
+ if (
270
+ lastError.message.includes(
271
+ 'TrezorConnect has been already initialized'
272
+ )
273
+ ) {
274
+ // Instead of just marking as connected, verify the connection
275
+ try {
276
+ // Test the connection with a simple call
277
+ const testResponse = await TrezorConnect.getFeatures();
278
+ if (testResponse.success) {
279
+ entry.status = ConnectionStatus.CONNECTED;
280
+ entry.retryCount = 0;
281
+ entry.error = undefined;
282
+ this.emit('connected', { type: HardwareWalletType.TREZOR });
283
+ return true;
284
+ }
285
+ } catch (testError) {
286
+ console.log(
287
+ '[HardwareWalletManager] Trezor test connection failed:',
288
+ testError
289
+ );
290
+ }
291
+
292
+ // If test failed, dispose and retry
293
+ try {
294
+ await TrezorConnect.dispose();
295
+ await this.delay(500);
296
+ } catch (disposeError) {
297
+ console.log(
298
+ '[HardwareWalletManager] Error disposing for retry:',
299
+ disposeError
300
+ );
301
+ }
302
+ }
303
+
304
+ entry.retryCount = attempt + 1;
305
+ entry.error = lastError;
306
+
307
+ // Check for specific Trezor errors
308
+ if (
309
+ lastError.message.includes('device is already in use') ||
310
+ lastError.message.includes('Device is being used in another window')
311
+ ) {
312
+ console.log(
313
+ '[HardwareWalletManager] Trezor device is in use by another application'
314
+ );
315
+
316
+ // Try to dispose and reinitialize
317
+ try {
318
+ await TrezorConnect.dispose();
319
+ await this.delay(2000); // Wait 2 seconds before retry
320
+ } catch (disposeError) {
321
+ console.log(
322
+ '[HardwareWalletManager] Error during dispose:',
323
+ disposeError
324
+ );
325
+ }
326
+ }
327
+
328
+ // Don't retry if user cancelled
329
+ if (
330
+ lastError.message.includes('Popup closed') ||
331
+ lastError.message.includes('cancelled') ||
332
+ lastError.message.includes('denied')
333
+ ) {
334
+ console.log(
335
+ '[HardwareWalletManager] User cancelled Trezor connection'
336
+ );
337
+ break; // Exit retry loop
338
+ }
339
+
340
+ if (attempt < trezorRetryConfig.maxRetries) {
341
+ const delay = this.calculateBackoffDelay(attempt);
342
+
343
+ this.emit('retrying', {
344
+ type: HardwareWalletType.TREZOR,
345
+ attempt: attempt + 1,
346
+ nextRetryIn: delay,
347
+ error: lastError.message,
348
+ });
349
+
350
+ await this.delay(delay);
351
+ }
352
+ }
353
+ }
354
+
355
+ entry.status = ConnectionStatus.ERROR;
356
+ this.emit('connectionFailed', {
357
+ type: HardwareWalletType.TREZOR,
358
+ error: lastError?.message,
359
+ });
360
+
361
+ // Clean up on failure
362
+ try {
363
+ await TrezorConnect.dispose();
364
+ } catch (disposeError) {
365
+ console.log(
366
+ '[HardwareWalletManager] Failed to dispose on error:',
367
+ disposeError
368
+ );
369
+ }
370
+
371
+ return false;
372
+ }
373
+
374
+ /**
375
+ * Handle device disconnect
376
+ */
377
+ private handleDisconnect(key: string, type: HardwareWalletType): void {
378
+ const entry = this.connectionPool.get(key);
379
+ if (entry) {
380
+ entry.status = ConnectionStatus.DISCONNECTED;
381
+ entry.transport = null;
382
+ this.emit('disconnected', { type });
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Start real-time status monitoring
388
+ */
389
+ private startStatusMonitoring(): void {
390
+ if (this.statusMonitorInterval) {
391
+ return;
392
+ }
393
+
394
+ this.statusMonitorInterval = setInterval(() => {
395
+ this.checkAllConnections();
396
+ }, this.STATUS_CHECK_INTERVAL);
397
+ }
398
+
399
+ /**
400
+ * Check all connections and clean up idle ones
401
+ */
402
+ private async checkAllConnections(): Promise<void> {
403
+ const now = Date.now();
404
+ const statuses: HardwareWalletStatus[] = [];
405
+
406
+ for (const [key, entry] of this.connectionPool.entries()) {
407
+ const type = key.startsWith(HardwareWalletType.LEDGER)
408
+ ? HardwareWalletType.LEDGER
409
+ : HardwareWalletType.TREZOR;
410
+
411
+ // Clean up idle connections
412
+ if (
413
+ entry.status === ConnectionStatus.CONNECTED &&
414
+ now - entry.lastActivity > this.IDLE_TIMEOUT
415
+ ) {
416
+ await this.closeConnection(key);
417
+ continue;
418
+ }
419
+
420
+ // Build status
421
+ const status: HardwareWalletStatus = {
422
+ type,
423
+ status: entry.status,
424
+ connected: entry.status === ConnectionStatus.CONNECTED,
425
+ lastSeen: entry.lastActivity,
426
+ error: entry.error?.message,
427
+ };
428
+
429
+ statuses.push(status);
430
+ }
431
+
432
+ this.emit('statusUpdate', statuses);
433
+ }
434
+
435
+ /**
436
+ * Get current status of all hardware wallets
437
+ */
438
+ getStatus(): HardwareWalletStatus[] {
439
+ const statuses: HardwareWalletStatus[] = [];
440
+
441
+ // Check Ledger
442
+ const ledgerEntry = this.connectionPool.get(
443
+ `${HardwareWalletType.LEDGER}-default`
444
+ );
445
+ statuses.push({
446
+ type: HardwareWalletType.LEDGER,
447
+ status: ledgerEntry?.status || ConnectionStatus.DISCONNECTED,
448
+ connected: ledgerEntry?.status === ConnectionStatus.CONNECTED,
449
+ lastSeen: ledgerEntry?.lastActivity,
450
+ error: ledgerEntry?.error?.message,
451
+ });
452
+
453
+ // Check Trezor
454
+ const trezorEntry = this.connectionPool.get(
455
+ `${HardwareWalletType.TREZOR}-default`
456
+ );
457
+ statuses.push({
458
+ type: HardwareWalletType.TREZOR,
459
+ status: trezorEntry?.status || ConnectionStatus.DISCONNECTED,
460
+ connected: trezorEntry?.status === ConnectionStatus.CONNECTED,
461
+ lastSeen: trezorEntry?.lastActivity,
462
+ error: trezorEntry?.error?.message,
463
+ });
464
+
465
+ return statuses;
466
+ }
467
+
468
+ /**
469
+ * Check if a specific device is connected
470
+ */
471
+ isConnected(type: HardwareWalletType): boolean {
472
+ const key = `${type}-default`;
473
+ const entry = this.connectionPool.get(key);
474
+ return entry?.status === ConnectionStatus.CONNECTED;
475
+ }
476
+
477
+ /**
478
+ * Ensure connection before operation
479
+ */
480
+ async ensureConnection(type: HardwareWalletType): Promise<void> {
481
+ if (type === HardwareWalletType.LEDGER) {
482
+ await this.getLedgerConnection();
483
+ } else if (type === HardwareWalletType.TREZOR) {
484
+ const initialized = await this.initializeTrezor();
485
+ if (!initialized) {
486
+ throw new Error('Failed to initialize Trezor');
487
+ }
488
+ }
489
+ }
490
+
491
+ /**
492
+ * Close a specific connection
493
+ */
494
+ private async closeConnection(key: string): Promise<void> {
495
+ const entry = this.connectionPool.get(key);
496
+
497
+ // Handle Trezor connections
498
+ if (key.startsWith(HardwareWalletType.TREZOR)) {
499
+ try {
500
+ await TrezorConnect.dispose();
501
+ } catch (error) {
502
+ console.error('Error disposing Trezor:', error);
503
+ }
504
+ }
505
+
506
+ // Handle Ledger connections
507
+ if (entry?.transport) {
508
+ try {
509
+ await entry.transport.close();
510
+ } catch (error) {
511
+ console.error('Error closing transport:', error);
512
+ }
513
+ }
514
+
515
+ this.connectionPool.delete(key);
516
+ }
517
+
518
+ /**
519
+ * Close all connections and stop monitoring
520
+ */
521
+ async destroy(): Promise<void> {
522
+ // Stop monitoring
523
+ if (this.statusMonitorInterval) {
524
+ clearInterval(this.statusMonitorInterval);
525
+ this.statusMonitorInterval = null;
526
+ }
527
+
528
+ // Close all connections
529
+ for (const key of this.connectionPool.keys()) {
530
+ await this.closeConnection(key);
531
+ }
532
+
533
+ this.removeAllListeners();
534
+ }
535
+
536
+ /**
537
+ * Calculate exponential backoff delay
538
+ */
539
+ private calculateBackoffDelay(attempt: number): number {
540
+ const delay = Math.min(
541
+ this.retryConfig.baseDelay *
542
+ Math.pow(this.retryConfig.backoffMultiplier, attempt),
543
+ this.retryConfig.maxDelay
544
+ );
545
+ // Add jitter (±20%)
546
+ const jitter = delay * 0.2 * (Math.random() - 0.5);
547
+ return Math.round(delay + jitter);
548
+ }
549
+
550
+ /**
551
+ * Create a timeout promise
552
+ */
553
+ private createTimeoutPromise<T>(ms: number, message: string): Promise<T> {
554
+ return new Promise((_, reject) => {
555
+ setTimeout(() => reject(new Error(message)), ms);
556
+ });
557
+ }
558
+
559
+ /**
560
+ * Delay helper
561
+ */
562
+ private delay(ms: number): Promise<void> {
563
+ return new Promise((resolve) => setTimeout(resolve, ms));
564
+ }
565
+
566
+ /**
567
+ * Retry an operation with exponential backoff
568
+ */
569
+ async retryOperation<T>(
570
+ operation: () => Promise<T>,
571
+ operationName: string,
572
+ customRetryConfig?: Partial<RetryConfig>
573
+ ): Promise<T> {
574
+ const config = { ...this.retryConfig, ...customRetryConfig };
575
+ let lastError: Error | undefined;
576
+
577
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
578
+ try {
579
+ return await operation();
580
+ } catch (error) {
581
+ lastError = error as Error;
582
+
583
+ // Do not retry on explicit user cancellations (Ledger/Trezor)
584
+ if (this.isUserCancellationError(lastError)) {
585
+ throw lastError;
586
+ }
587
+
588
+ if (attempt < config.maxRetries) {
589
+ const delay = this.calculateBackoffDelay(attempt);
590
+
591
+ this.emit('operationRetry', {
592
+ operation: operationName,
593
+ attempt: attempt + 1,
594
+ nextRetryIn: delay,
595
+ error: lastError.message,
596
+ });
597
+
598
+ await this.delay(delay);
599
+ }
600
+ }
601
+ }
602
+
603
+ throw new Error(
604
+ `${operationName} failed after ${config.maxRetries + 1} attempts: ${
605
+ lastError?.message || 'Unknown error'
606
+ }`
607
+ );
608
+ }
609
+
610
+ /**
611
+ * Detects common user-cancellation errors to avoid pointless retries
612
+ */
613
+ private isUserCancellationError(err: Error): boolean {
614
+ const message = (err?.message || '').toLowerCase();
615
+ const name = (err as any)?.name || '';
616
+ const statusCode = (err as any)?.statusCode;
617
+
618
+ // Ledger: 0x6985 (Conditions of use not satisfied) on rejection
619
+ if (statusCode === 0x6985) return true;
620
+
621
+ // Message-based heuristics across vendors
622
+ if (
623
+ message.includes('0x6985') ||
624
+ (message.includes('condition') &&
625
+ message.includes('not') &&
626
+ message.includes('satisf')) ||
627
+ message.includes('not allowed') ||
628
+ message.includes('permission denied') ||
629
+ message.includes('user rejected') ||
630
+ message.includes('denied by the user') ||
631
+ message.includes('failure_actioncancelled') ||
632
+ message.includes('denied') ||
633
+ message.includes('rejected') ||
634
+ message.includes('refused') ||
635
+ message.includes('cancelled') ||
636
+ message.includes('canceled') ||
637
+ message.includes('popup closed')
638
+ ) {
639
+ return true;
640
+ }
641
+
642
+ // Name-based checks (align with frontend utilities)
643
+ if (
644
+ name === 'NotAllowedError' ||
645
+ name === 'AbortError' ||
646
+ name === 'UserCancel' ||
647
+ name === 'TransportError' ||
648
+ name === 'DOMException'
649
+ ) {
650
+ return true;
651
+ }
652
+
653
+ return false;
654
+ }
655
+ }
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ export * from './transactions';
2
+ export * from './trezor';
3
+ export * from './types';
4
+ export * from './storage';
5
+ export * from './signers';
6
+ export * from './initial-state';
7
+ export * from './keyring-manager';
8
+ export * from './providers';
9
+ export * from './utils/derivation-paths';
10
+ export * from './network-utils';
11
+ export * from './hardware-wallet-manager';
12
+ export { PsbtUtils } from './utils/psbt';