@sidhujag/sysweb3-keyring 1.0.545 → 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.
- package/coverage/clover.xml +2875 -0
- package/coverage/coverage-final.json +29468 -0
- package/coverage/lcov-report/base.css +354 -0
- package/coverage/lcov-report/block-navigation.js +85 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +320 -0
- package/coverage/lcov-report/prettify.css +101 -0
- package/coverage/lcov-report/prettify.js +1008 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +191 -0
- package/coverage/lcov-report/src/index.html +276 -0
- package/coverage/lcov-report/src/index.ts.html +114 -0
- package/coverage/lcov-report/src/initial-state.ts.html +558 -0
- package/coverage/lcov-report/src/keyring-manager.ts.html +6279 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/index.html +178 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/index.ts.html +144 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/appClient.ts.html +1560 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/bip32.ts.html +276 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/buffertools.ts.html +495 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/clientCommands.ts.html +1138 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/index.html +363 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkelizedPsbt.ts.html +289 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkle.ts.html +486 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkleMap.ts.html +240 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/policy.ts.html +342 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/psbtv2.ts.html +2388 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/varint.ts.html +453 -0
- package/coverage/lcov-report/src/ledger/consts.ts.html +177 -0
- package/coverage/lcov-report/src/ledger/index.html +216 -0
- package/coverage/lcov-report/src/ledger/index.ts.html +1371 -0
- package/coverage/lcov-report/src/ledger/utils.ts.html +102 -0
- package/coverage/lcov-report/src/signers.ts.html +591 -0
- package/coverage/lcov-report/src/storage.ts.html +198 -0
- package/coverage/lcov-report/src/transactions/ethereum.ts.html +5826 -0
- package/coverage/lcov-report/src/transactions/index.html +216 -0
- package/coverage/lcov-report/src/transactions/index.ts.html +93 -0
- package/coverage/lcov-report/src/transactions/syscoin.ts.html +1521 -0
- package/coverage/lcov-report/src/trezor/index.html +176 -0
- package/coverage/lcov-report/src/trezor/index.ts.html +2655 -0
- package/coverage/lcov-report/src/types.ts.html +1443 -0
- package/coverage/lcov-report/src/utils/derivation-paths.ts.html +486 -0
- package/coverage/lcov-report/src/utils/index.html +196 -0
- package/coverage/lcov-report/src/utils/psbt.ts.html +159 -0
- package/coverage/lcov-report/test/helpers/constants.ts.html +627 -0
- package/coverage/lcov-report/test/helpers/index.html +176 -0
- package/coverage/lcov.info +4832 -0
- package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/appClient.js +1 -124
- package/dist/cjs/ledger/bitcoin_client/lib/appClient.js.map +1 -0
- package/{cjs → dist/cjs}/transactions/ethereum.js +6 -2
- package/dist/cjs/transactions/ethereum.js.map +1 -0
- package/dist/package.json +50 -0
- package/{types → dist/types}/ledger/bitcoin_client/lib/appClient.d.ts +0 -6
- package/examples/basic-usage.js +140 -0
- package/jest.config.js +32 -0
- package/package.json +31 -13
- package/readme.md +201 -0
- package/src/declare.d.ts +7 -0
- package/src/errorUtils.ts +83 -0
- package/src/hardware-wallet-manager.ts +655 -0
- package/src/index.ts +12 -0
- package/src/initial-state.ts +108 -0
- package/src/keyring-manager.ts +2698 -0
- package/src/ledger/bitcoin_client/index.ts +19 -0
- package/src/ledger/bitcoin_client/lib/appClient.ts +405 -0
- package/src/ledger/bitcoin_client/lib/bip32.ts +61 -0
- package/src/ledger/bitcoin_client/lib/buffertools.ts +134 -0
- package/src/ledger/bitcoin_client/lib/clientCommands.ts +356 -0
- package/src/ledger/bitcoin_client/lib/constants.ts +12 -0
- package/src/ledger/bitcoin_client/lib/merkelizedPsbt.ts +65 -0
- package/src/ledger/bitcoin_client/lib/merkle.ts +136 -0
- package/src/ledger/bitcoin_client/lib/merkleMap.ts +49 -0
- package/src/ledger/bitcoin_client/lib/policy.ts +91 -0
- package/src/ledger/bitcoin_client/lib/psbtv2.ts +768 -0
- package/src/ledger/bitcoin_client/lib/varint.ts +120 -0
- package/src/ledger/consts.ts +3 -0
- package/src/ledger/index.ts +685 -0
- package/src/ledger/types.ts +74 -0
- package/src/network-utils.ts +99 -0
- package/src/providers.ts +345 -0
- package/src/signers.ts +158 -0
- package/src/storage.ts +63 -0
- package/src/transactions/__tests__/integration.test.ts +303 -0
- package/src/transactions/__tests__/syscoin.test.ts +409 -0
- package/src/transactions/ethereum.ts +2503 -0
- package/src/transactions/index.ts +2 -0
- package/src/transactions/syscoin.ts +542 -0
- package/src/trezor/index.ts +1050 -0
- package/src/types.ts +366 -0
- package/src/utils/derivation-paths.ts +133 -0
- package/src/utils/psbt.ts +24 -0
- package/src/utils.ts +191 -0
- package/test/README.md +158 -0
- package/test/__mocks__/ledger-mock.js +20 -0
- package/test/__mocks__/trezor-mock.js +75 -0
- package/test/cleanup-summary.md +167 -0
- package/test/helpers/README.md +78 -0
- package/test/helpers/constants.ts +79 -0
- package/test/helpers/setup.ts +714 -0
- package/test/integration/import-validation.spec.ts +588 -0
- package/test/unit/hardware/ledger.spec.ts +869 -0
- package/test/unit/hardware/trezor.spec.ts +828 -0
- package/test/unit/keyring-manager/account-management.spec.ts +970 -0
- package/test/unit/keyring-manager/import-watchonly.spec.ts +181 -0
- package/test/unit/keyring-manager/import-wif.spec.ts +126 -0
- package/test/unit/keyring-manager/initialization.spec.ts +782 -0
- package/test/unit/keyring-manager/key-derivation.spec.ts +996 -0
- package/test/unit/keyring-manager/security.spec.ts +505 -0
- package/test/unit/keyring-manager/state-management.spec.ts +375 -0
- package/test/unit/network/network-management.spec.ts +372 -0
- package/test/unit/transactions/ethereum-transactions.spec.ts +382 -0
- package/test/unit/transactions/syscoin-transactions.spec.ts +615 -0
- package/tsconfig.json +14 -0
- package/cjs/ledger/bitcoin_client/lib/appClient.js.map +0 -1
- package/cjs/transactions/ethereum.js.map +0 -1
- /package/{README.md → dist/README.md} +0 -0
- /package/{cjs → dist/cjs}/errorUtils.js +0 -0
- /package/{cjs → dist/cjs}/errorUtils.js.map +0 -0
- /package/{cjs → dist/cjs}/hardware-wallet-manager.js +0 -0
- /package/{cjs → dist/cjs}/hardware-wallet-manager.js.map +0 -0
- /package/{cjs → dist/cjs}/index.js +0 -0
- /package/{cjs → dist/cjs}/index.js.map +0 -0
- /package/{cjs → dist/cjs}/initial-state.js +0 -0
- /package/{cjs → dist/cjs}/initial-state.js.map +0 -0
- /package/{cjs → dist/cjs}/keyring-manager.js +0 -0
- /package/{cjs → dist/cjs}/keyring-manager.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/index.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/index.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/bip32.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/bip32.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/buffertools.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/buffertools.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/clientCommands.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/clientCommands.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/constants.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/constants.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkelizedPsbt.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkelizedPsbt.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkle.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkle.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkleMap.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkleMap.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/policy.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/policy.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/psbtv2.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/psbtv2.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/varint.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/varint.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/consts.js +0 -0
- /package/{cjs → dist/cjs}/ledger/consts.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/index.js +0 -0
- /package/{cjs → dist/cjs}/ledger/index.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/types.js +0 -0
- /package/{cjs → dist/cjs}/ledger/types.js.map +0 -0
- /package/{cjs → dist/cjs}/network-utils.js +0 -0
- /package/{cjs → dist/cjs}/network-utils.js.map +0 -0
- /package/{cjs → dist/cjs}/providers.js +0 -0
- /package/{cjs → dist/cjs}/providers.js.map +0 -0
- /package/{cjs → dist/cjs}/signers.js +0 -0
- /package/{cjs → dist/cjs}/signers.js.map +0 -0
- /package/{cjs → dist/cjs}/storage.js +0 -0
- /package/{cjs → dist/cjs}/storage.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/integration.test.js +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/integration.test.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/syscoin.test.js +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/syscoin.test.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/index.js +0 -0
- /package/{cjs → dist/cjs}/transactions/index.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/syscoin.js +0 -0
- /package/{cjs → dist/cjs}/transactions/syscoin.js.map +0 -0
- /package/{cjs → dist/cjs}/trezor/index.js +0 -0
- /package/{cjs → dist/cjs}/trezor/index.js.map +0 -0
- /package/{cjs → dist/cjs}/types.js +0 -0
- /package/{cjs → dist/cjs}/types.js.map +0 -0
- /package/{cjs → dist/cjs}/utils/derivation-paths.js +0 -0
- /package/{cjs → dist/cjs}/utils/derivation-paths.js.map +0 -0
- /package/{cjs → dist/cjs}/utils/psbt.js +0 -0
- /package/{cjs → dist/cjs}/utils/psbt.js.map +0 -0
- /package/{cjs → dist/cjs}/utils.js +0 -0
- /package/{cjs → dist/cjs}/utils.js.map +0 -0
- /package/{types → dist/types}/errorUtils.d.ts +0 -0
- /package/{types → dist/types}/hardware-wallet-manager.d.ts +0 -0
- /package/{types → dist/types}/index.d.ts +0 -0
- /package/{types → dist/types}/initial-state.d.ts +0 -0
- /package/{types → dist/types}/keyring-manager.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/index.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/bip32.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/buffertools.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/clientCommands.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/constants.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/merkelizedPsbt.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/merkle.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/merkleMap.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/policy.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/psbtv2.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/varint.d.ts +0 -0
- /package/{types → dist/types}/ledger/consts.d.ts +0 -0
- /package/{types → dist/types}/ledger/index.d.ts +0 -0
- /package/{types → dist/types}/ledger/types.d.ts +0 -0
- /package/{types → dist/types}/network-utils.d.ts +0 -0
- /package/{types → dist/types}/providers.d.ts +0 -0
- /package/{types → dist/types}/signers.d.ts +0 -0
- /package/{types → dist/types}/storage.d.ts +0 -0
- /package/{types → dist/types}/transactions/__tests__/integration.test.d.ts +0 -0
- /package/{types → dist/types}/transactions/__tests__/syscoin.test.d.ts +0 -0
- /package/{types → dist/types}/transactions/ethereum.d.ts +0 -0
- /package/{types → dist/types}/transactions/index.d.ts +0 -0
- /package/{types → dist/types}/transactions/syscoin.d.ts +0 -0
- /package/{types → dist/types}/trezor/index.d.ts +0 -0
- /package/{types → dist/types}/types.d.ts +0 -0
- /package/{types → dist/types}/utils/derivation-paths.d.ts +0 -0
- /package/{types → dist/types}/utils/psbt.d.ts +0 -0
- /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';
|