@sidhujag/sysweb3-keyring 1.0.628 → 2.1.0

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 (131) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +174 -0
  3. package/cjs/bip84-native.js +8 -0
  4. package/cjs/bip84-native.js.map +1 -0
  5. package/cjs/hardware-wallet-manager.js +302 -128
  6. package/cjs/hardware-wallet-manager.js.map +1 -1
  7. package/cjs/keyring-core.js +319 -0
  8. package/cjs/keyring-core.js.map +1 -1
  9. package/cjs/keyring-manager.js +9 -3
  10. package/cjs/keyring-manager.js.map +1 -1
  11. package/cjs/ledger/bitcoin_client/lib/appClient.js.map +1 -1
  12. package/cjs/ledger/host-types.js +10 -0
  13. package/cjs/ledger/host-types.js.map +1 -0
  14. package/cjs/ledger/index.js +243 -447
  15. package/cjs/ledger/index.js.map +1 -1
  16. package/cjs/ledger/prepare-psbt.js +177 -0
  17. package/cjs/ledger/prepare-psbt.js.map +1 -0
  18. package/cjs/public-bip84.js +157 -0
  19. package/cjs/public-bip84.js.map +1 -0
  20. package/cjs/software-hd-account.js +23 -0
  21. package/cjs/software-hd-account.js.map +1 -0
  22. package/cjs/software-utxo-account.js +12 -0
  23. package/cjs/software-utxo-account.js.map +1 -0
  24. package/cjs/software.js +24 -0
  25. package/cjs/software.js.map +1 -1
  26. package/cjs/transactions/bip84-native-plan.js +481 -0
  27. package/cjs/transactions/bip84-native-plan.js.map +1 -0
  28. package/cjs/transactions/ethereum.js +55 -10
  29. package/cjs/transactions/ethereum.js.map +1 -1
  30. package/cjs/transactions/hardware-psbt.js +36 -44
  31. package/cjs/transactions/hardware-psbt.js.map +1 -1
  32. package/cjs/transactions/prepared-bip84.js +214 -0
  33. package/cjs/transactions/prepared-bip84.js.map +1 -0
  34. package/cjs/transactions/prepared-evm.js +197 -0
  35. package/cjs/transactions/prepared-evm.js.map +1 -0
  36. package/cjs/trezor/connect.js +243 -0
  37. package/cjs/trezor/connect.js.map +1 -0
  38. package/cjs/trezor/index.js +96 -58
  39. package/cjs/trezor/index.js.map +1 -1
  40. package/cjs/trezor/witness.js +25 -0
  41. package/cjs/trezor/witness.js.map +1 -0
  42. package/cjs/utils/psbt-account-scope.js +5 -3
  43. package/cjs/utils/psbt-account-scope.js.map +1 -1
  44. package/esm/errorUtils.js +72 -0
  45. package/esm/errorUtils.js.map +1 -0
  46. package/esm/ethers-v6.js +207 -0
  47. package/esm/ethers-v6.js.map +1 -0
  48. package/esm/hardware-wallet-manager.js +643 -0
  49. package/esm/hardware-wallet-manager.js.map +1 -0
  50. package/esm/ledger/bitcoin_client/lib/appClient.js +279 -0
  51. package/esm/ledger/bitcoin_client/lib/appClient.js.map +1 -0
  52. package/esm/ledger/bitcoin_client/lib/bip32.js +49 -0
  53. package/esm/ledger/bitcoin_client/lib/bip32.js.map +1 -0
  54. package/esm/ledger/bitcoin_client/lib/buffertools.js +119 -0
  55. package/esm/ledger/bitcoin_client/lib/buffertools.js.map +1 -0
  56. package/esm/ledger/bitcoin_client/lib/clientCommands.js +269 -0
  57. package/esm/ledger/bitcoin_client/lib/clientCommands.js.map +1 -0
  58. package/esm/ledger/bitcoin_client/lib/merkelizedPsbt.js +50 -0
  59. package/esm/ledger/bitcoin_client/lib/merkelizedPsbt.js.map +1 -0
  60. package/esm/ledger/bitcoin_client/lib/merkle.js +111 -0
  61. package/esm/ledger/bitcoin_client/lib/merkle.js.map +1 -0
  62. package/esm/ledger/bitcoin_client/lib/merkleMap.js +42 -0
  63. package/esm/ledger/bitcoin_client/lib/merkleMap.js.map +1 -0
  64. package/esm/ledger/bitcoin_client/lib/policy.js +68 -0
  65. package/esm/ledger/bitcoin_client/lib/policy.js.map +1 -0
  66. package/esm/ledger/bitcoin_client/lib/psbtv2.js +645 -0
  67. package/esm/ledger/bitcoin_client/lib/psbtv2.js.map +1 -0
  68. package/esm/ledger/bitcoin_client/lib/varint.js +108 -0
  69. package/esm/ledger/bitcoin_client/lib/varint.js.map +1 -0
  70. package/esm/ledger/consts.js +4 -0
  71. package/esm/ledger/consts.js.map +1 -0
  72. package/esm/ledger/dmk-evm.js +258 -0
  73. package/esm/ledger/dmk-evm.js.map +1 -0
  74. package/esm/ledger/dmk-session.js +264 -0
  75. package/esm/ledger/dmk-session.js.map +1 -0
  76. package/esm/ledger/dmk-utxo.js +279 -0
  77. package/esm/ledger/dmk-utxo.js.map +1 -0
  78. package/esm/ledger/host-types.js +5 -0
  79. package/esm/ledger/host-types.js.map +1 -0
  80. package/esm/ledger/index.js +268 -0
  81. package/esm/ledger/index.js.map +1 -0
  82. package/esm/ledger/prepare-psbt.js +141 -0
  83. package/esm/ledger/prepare-psbt.js.map +1 -0
  84. package/esm/ledger/types.js +2 -0
  85. package/esm/ledger/types.js.map +1 -0
  86. package/esm/ledger-host.js +36 -0
  87. package/esm/ledger-host.js.map +1 -0
  88. package/esm/private-records.js +153 -0
  89. package/esm/private-records.js.map +1 -0
  90. package/esm/providers.js +588 -0
  91. package/esm/providers.js.map +1 -0
  92. package/esm/signers.js +28 -0
  93. package/esm/signers.js.map +1 -0
  94. package/esm/trezor/connect.js +206 -0
  95. package/esm/trezor/connect.js.map +1 -0
  96. package/esm/trezor/index.js +876 -0
  97. package/esm/trezor/index.js.map +1 -0
  98. package/esm/trezor/witness.js +21 -0
  99. package/esm/trezor/witness.js.map +1 -0
  100. package/esm/types.js +17 -0
  101. package/esm/types.js.map +1 -0
  102. package/esm/utils/derivation-paths.js +109 -0
  103. package/esm/utils/derivation-paths.js.map +1 -0
  104. package/esm/utils/psbt-account-scope.js +310 -0
  105. package/esm/utils/psbt-account-scope.js.map +1 -0
  106. package/package.json +34 -9
  107. package/types/bip84-native.d.ts +3 -0
  108. package/types/hardware-wallet-manager.d.ts +12 -19
  109. package/types/keyring-core.d.ts +14 -0
  110. package/types/keyring-manager.d.ts +2 -1
  111. package/types/ledger/bitcoin_client/lib/appClient.d.ts +6 -3
  112. package/types/ledger/dmk-evm.d.ts +8 -0
  113. package/types/ledger/dmk-session.d.ts +60 -0
  114. package/types/ledger/dmk-utxo.d.ts +26 -0
  115. package/types/ledger/host-types.d.ts +131 -0
  116. package/types/ledger/index.d.ts +21 -58
  117. package/types/ledger/prepare-psbt.d.ts +3 -0
  118. package/types/ledger/types.d.ts +3 -0
  119. package/types/ledger-host.d.ts +7 -0
  120. package/types/public-bip84.d.ts +39 -0
  121. package/types/software-hd-account.d.ts +16 -0
  122. package/types/software-utxo-account.d.ts +13 -0
  123. package/types/software.d.ts +26 -0
  124. package/types/transactions/bip84-native-plan.d.ts +83 -0
  125. package/types/transactions/ethereum.d.ts +1 -1
  126. package/types/transactions/prepared-bip84.d.ts +27 -0
  127. package/types/transactions/prepared-evm.d.ts +53 -0
  128. package/types/trezor/connect.d.ts +42 -0
  129. package/types/trezor/index.d.ts +22 -4
  130. package/types/trezor/witness.d.ts +3 -0
  131. package/types/utils/psbt-account-scope.d.ts +1 -0
@@ -0,0 +1,876 @@
1
+ /* eslint-disable camelcase */
2
+ import { TypedDataUtils, SignTypedDataVersion, } from '@metamask/eth-sig-util';
3
+ import bitcoinops from 'bitcoin-ops';
4
+ import bs58check from 'bs58check';
5
+ import { Buffer } from 'buffer';
6
+ import * as syscoinjs from 'syscoinjs-lib';
7
+ import TrezorConnect, { subscribeTrezorEvents, trezorResponseError, } from './connect';
8
+ import { serializeWitness } from './witness';
9
+ import { HardwareWalletManager, HardwareWalletType, } from '../hardware-wallet-manager';
10
+ import { getAccountDerivationPath, getAddressDerivationPath, isEvmCoin, } from '../utils/derivation-paths';
11
+ const bitcoinjs = syscoinjs.utils.bitcoinjs;
12
+ const { p2wsh } = bitcoinjs.payments;
13
+ const { decompile } = bitcoinjs.script;
14
+ const { fromBech32 } = bitcoinjs.address;
15
+ const fromBase58Check = (value) => {
16
+ try {
17
+ return bitcoinjs.address.fromBase58Check(value);
18
+ }
19
+ catch (error) {
20
+ // The former decoder also accepted a two-byte network version. Preserve
21
+ // exactly that checksum-verified format without loading the legacy fork.
22
+ const payload = bs58check.decode(value);
23
+ if (payload.length === 22) {
24
+ return {
25
+ version: (payload[0] << 8) | payload[1],
26
+ hash: payload.subarray(2),
27
+ };
28
+ }
29
+ throw error;
30
+ }
31
+ };
32
+ const initialHDPath = `m/44'/60'/0'/0/0`;
33
+ const DELAY_BETWEEN_POPUPS = 2000; // Increased from 1000ms to 2000ms for more reliable operation
34
+ const stripHexPrefix = (value) => value.startsWith('0x') || value.startsWith('0X') ? value.slice(2) : value;
35
+ const finalizedFields = (input) => {
36
+ if (!input.finalScriptSig && !input.finalScriptWitness)
37
+ return null;
38
+ return {
39
+ scriptSig: Buffer.from(input.finalScriptSig || []).toString('hex'),
40
+ witness: input.finalScriptWitness
41
+ ? Buffer.from(input.finalScriptWitness).toString('hex')
42
+ : '00',
43
+ };
44
+ };
45
+ export class TrezorKeyring {
46
+ constructor() {
47
+ this.hdPath = initialHDPath;
48
+ this.paths = {};
49
+ this.initialized = false;
50
+ this.lifecycleGeneration = 0;
51
+ this.onTrezorConnected = ({ type, }) => {
52
+ if (type === HardwareWalletType.TREZOR) {
53
+ console.log('Trezor connected');
54
+ this.initialized = true;
55
+ }
56
+ };
57
+ this.onTrezorDisconnected = ({ type, }) => {
58
+ if (type === HardwareWalletType.TREZOR) {
59
+ console.log('Trezor disconnected');
60
+ this.initialized = false;
61
+ }
62
+ };
63
+ this._transformTypedData = (data, version) => {
64
+ const { types, primaryType, domain, message } = this._sanitizeData(data);
65
+ const domainSeparatorHash = TypedDataUtils.hashStruct('EIP712Domain', this._sanitizeData(domain), types, version).toString('hex');
66
+ let messageHash = null;
67
+ if (primaryType !== 'EIP712Domain') {
68
+ messageHash = TypedDataUtils.hashStruct(primaryType, this._sanitizeData(message), types, version).toString('hex');
69
+ }
70
+ return {
71
+ domain_separator_hash: domainSeparatorHash,
72
+ message_hash: messageHash,
73
+ ...data,
74
+ };
75
+ };
76
+ this.publicKey = Buffer.from('', 'hex');
77
+ this.chainCode = Buffer.from('', 'hex');
78
+ this.hdPath = '';
79
+ this.paths = {};
80
+ this.hardwareWalletManager = new HardwareWalletManager();
81
+ this.observeTrezor();
82
+ }
83
+ observeTrezor() {
84
+ // Manager destruction removes its listeners. Reusing an unlocked keyring
85
+ // must restore these same callbacks without multiplying them on each init.
86
+ if (!this.hardwareWalletManager
87
+ .listeners('connected')
88
+ .includes(this.onTrezorConnected))
89
+ this.hardwareWalletManager.on('connected', this.onTrezorConnected);
90
+ if (!this.hardwareWalletManager
91
+ .listeners('disconnected')
92
+ .includes(this.onTrezorDisconnected))
93
+ this.hardwareWalletManager.on('disconnected', this.onTrezorDisconnected);
94
+ if (!this.unsubscribeTrezor)
95
+ this.unsubscribeTrezor = subscribeTrezorEvents((event) => {
96
+ if (event.type === 'model')
97
+ this.model = event.model;
98
+ });
99
+ }
100
+ /**
101
+ * Initialize Trezor script.
102
+ */
103
+ async init() {
104
+ const generation = this.lifecycleGeneration;
105
+ this.observeTrezor();
106
+ if (this.initialized) {
107
+ return true;
108
+ }
109
+ // Add a small delay to ensure Chrome extension context is ready
110
+ // This helps prevent "waiting for handshake" errors
111
+ await new Promise((resolve) => setTimeout(resolve, 100));
112
+ if (generation !== this.lifecycleGeneration)
113
+ return false;
114
+ const result = await this.hardwareWalletManager.initializeTrezor();
115
+ if (generation !== this.lifecycleGeneration)
116
+ return false;
117
+ if (result) {
118
+ this.initialized = true;
119
+ }
120
+ return result;
121
+ }
122
+ /**
123
+ * Execute operation with automatic retry
124
+ */
125
+ async executeWithRetry(operation, operationName) {
126
+ // Ensure initialization first
127
+ if (!this.initialized) {
128
+ const initResult = await this.init();
129
+ if (!initResult) {
130
+ throw new Error('Failed to initialize Trezor');
131
+ }
132
+ }
133
+ // For Trezor operations, use reduced retry config to prevent popup spam
134
+ const trezorRetryConfig = {
135
+ maxRetries: 1, // Only retry once
136
+ baseDelay: 1000,
137
+ maxDelay: 5000,
138
+ backoffMultiplier: 2,
139
+ };
140
+ // Use hardware wallet manager's retry mechanism with custom config
141
+ return this.hardwareWalletManager
142
+ .retryOperation(operation, operationName, trezorRetryConfig)
143
+ .catch(async (error) => {
144
+ // Clean up Trezor state on failure
145
+ const message = String(error.message || '').toLowerCase();
146
+ if (message.includes('popup closed') ||
147
+ message.includes('cancelled') ||
148
+ message.includes('canceled') ||
149
+ message.includes('denied')) {
150
+ this.initialized = false;
151
+ // Dispose Trezor connection to clean up
152
+ try {
153
+ await TrezorConnect.dispose();
154
+ }
155
+ catch (disposeError) {
156
+ console.log('Failed to dispose Trezor on error:', disposeError);
157
+ }
158
+ }
159
+ throw error;
160
+ });
161
+ }
162
+ /**
163
+ * This return account info based in params provided.
164
+ *
165
+ * @param coin - network symbol. Example: eth, sys, btc
166
+ * @param slip44 - network slip44 number
167
+ * @param hdPath - path derivation. Example: m/84'/57'/0'
168
+ * @param index - index of account for path derivation
169
+ * @returns derivated account info or error
170
+ */
171
+ async getAccountInfo({ coin, slip44, hdPath, index, }) {
172
+ return this.executeWithRetry(async () => {
173
+ // Use dynamic path generation instead of hardcoded switch
174
+ this.setHdPath(coin, index || 0, slip44);
175
+ if (hdPath)
176
+ this.hdPath = hdPath;
177
+ // For EVM networks, getAccountInfo is not supported
178
+ // We need to use getAddress instead
179
+ if (slip44 === 60) {
180
+ const addressResponse = await TrezorConnect.ethereumGetAddress({
181
+ path: this.hdPath,
182
+ showOnTrezor: false,
183
+ });
184
+ if (!addressResponse.success) {
185
+ throw trezorResponseError(addressResponse);
186
+ }
187
+ // Return a compatible AccountInfo structure
188
+ return {
189
+ descriptor: addressResponse.payload.address,
190
+ balance: '0', // Balance is fetched separately for EVM
191
+ empty: true,
192
+ history: {
193
+ total: 0,
194
+ unconfirmed: 0,
195
+ },
196
+ };
197
+ }
198
+ // For UTXO networks, use the standard getAccountInfo
199
+ const response = await TrezorConnect.getAccountInfo({
200
+ coin: coin,
201
+ path: this.hdPath,
202
+ });
203
+ if (response.success) {
204
+ return response.payload;
205
+ }
206
+ throw trezorResponseError(response);
207
+ }, 'getAccountInfo');
208
+ }
209
+ /**
210
+ * Gets the model, if known.
211
+ * This may be `undefined` if the model hasn't been loaded yet.
212
+ *
213
+ * @returns
214
+ */
215
+ getModel() {
216
+ return this.model;
217
+ }
218
+ /**
219
+ * This disposes the active Trezor Suite connection
220
+ *
221
+ * @returns void
222
+ */
223
+ dispose() {
224
+ this.lifecycleGeneration++;
225
+ try {
226
+ void TrezorConnect.dispose().catch((error) => {
227
+ console.log('Error disposing Trezor:', error);
228
+ });
229
+ this.initialized = false;
230
+ // Clear any cached data
231
+ this.publicKey = Buffer.from('', 'hex');
232
+ this.chainCode = Buffer.from('', 'hex');
233
+ this.hdPath = '';
234
+ this.paths = {};
235
+ }
236
+ catch (error) {
237
+ console.log('Error disposing Trezor:', error);
238
+ }
239
+ }
240
+ /**
241
+ * This verify if message is valid or not.
242
+ *
243
+ * @param coin - network symbol. Example: eth, sys, btc
244
+ * @param address - account address that signed message
245
+ * @param message - message to be verified. Example: 'Test message'
246
+ * @param signature - signature received in sign method. Example: I6BrpivjCwZmScZ6BMAHWGQPo+JjX2kzKXU5LcGVfEgvFb2VfJuKo3g6eSQcykQZiILoWNUDn5rDHkwJg3EcvuY=
247
+ * @returns derivated account info or error
248
+ */
249
+ async verifyMessage({ coin, address, message, signature, }) {
250
+ return this.executeWithRetry(async () => {
251
+ let method = '';
252
+ switch (coin) {
253
+ case 'eth':
254
+ method = 'ethereumVerifyMessage';
255
+ break;
256
+ default:
257
+ method = 'verifyMessage';
258
+ }
259
+ // @ts-ignore
260
+ const { success, payload } = await TrezorConnect[method]({
261
+ coin,
262
+ address,
263
+ message,
264
+ signature,
265
+ });
266
+ if (success) {
267
+ return { success, payload };
268
+ }
269
+ throw trezorResponseError({ payload });
270
+ }, 'verifyMessage');
271
+ }
272
+ /**
273
+ * This return account public key.
274
+ *
275
+ * @param coin - network symbol. Example: eth, sys, btc
276
+ * @param slip44 - network slip44 number
277
+ * @param hdPath - path derivation. Example: m/44'/57'/0'/0/0
278
+ * @returns publicKey and chainCode
279
+ */
280
+ async getPublicKey({ coin, slip44, hdPath, index, }) {
281
+ this.setHdPath(coin, index || 0, slip44);
282
+ if (hdPath)
283
+ this.hdPath = hdPath;
284
+ await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_POPUPS));
285
+ try {
286
+ // For EVM networks, use ethereumGetPublicKey
287
+ if (slip44 === 60) {
288
+ const { success, payload } = await TrezorConnect.ethereumGetPublicKey({
289
+ path: this.hdPath,
290
+ showOnTrezor: false,
291
+ });
292
+ if (success) {
293
+ const { publicKey } = payload;
294
+ // For Ethereum, we don't get chainCode from ethereumGetPublicKey
295
+ this.publicKey = Buffer.from(publicKey, 'hex');
296
+ return {
297
+ publicKey: `0x${publicKey}`,
298
+ chainCode: '', // Ethereum doesn't use chainCode in the same way
299
+ };
300
+ }
301
+ return { success: false, payload };
302
+ }
303
+ // For UTXO networks, use standard getPublicKey
304
+ const { success, payload } = await TrezorConnect.getPublicKey({
305
+ coin: coin,
306
+ path: this.hdPath,
307
+ });
308
+ if (success) {
309
+ const { publicKey, chainCode } = payload;
310
+ this.publicKey = Buffer.from(publicKey, 'hex');
311
+ this.chainCode = Buffer.from(chainCode, 'hex');
312
+ return {
313
+ publicKey: `0x${this.publicKey.toString('hex')}`,
314
+ chainCode: `0x${this.chainCode.toString('hex')}`,
315
+ };
316
+ }
317
+ return { success: false, payload };
318
+ }
319
+ catch (error) {
320
+ return error;
321
+ }
322
+ }
323
+ range(n) {
324
+ return [...Array(n).keys()];
325
+ }
326
+ /**
327
+ * This sign UTXO tx.
328
+ *
329
+ * @param coin - network symbol. Example: eth, sys, btc
330
+ * @param inputs - utxo transaction inputs
331
+ * @param outputs - utxo transaction outputs
332
+ * @returns signature object
333
+ */
334
+ async signUtxoTransaction(utxoTransaction, psbt) {
335
+ // Capture approval before the device await. Witnesses and scriptSigs are
336
+ // signatures, but every unsigned transaction field must remain identical.
337
+ const unsignedSnapshot = (transaction) => JSON.stringify({
338
+ version: transaction.version,
339
+ locktime: transaction.locktime,
340
+ inputs: (transaction.txInputs || transaction.ins).map((input) => ({
341
+ hash: Buffer.from(input.hash).toString('hex'),
342
+ index: input.index,
343
+ sequence: input.sequence,
344
+ })),
345
+ outputs: (transaction.txOutputs || transaction.outs).map((output) => ({
346
+ script: Buffer.from(output.script).toString('hex'),
347
+ value: output.value.toString(),
348
+ })),
349
+ });
350
+ const approvedTransaction = unsignedSnapshot(psbt);
351
+ const approvedFinalInputs = psbt.data.inputs.map(finalizedFields);
352
+ const payload = await this.executeWithRetry(async () => {
353
+ const response = await TrezorConnect.signTransaction(utxoTransaction);
354
+ if (!response.success) {
355
+ throw trezorResponseError(response, 'Trezor sign failed: ');
356
+ }
357
+ return response.payload;
358
+ }, 'signUtxoTransaction');
359
+ const tx = bitcoinjs.Transaction.fromHex(payload.serializedTx);
360
+ if (unsignedSnapshot(tx) !== approvedTransaction ||
361
+ unsignedSnapshot(psbt) !== approvedTransaction) {
362
+ throw new Error('Trezor returned a different unsigned transaction');
363
+ }
364
+ // Finalized inputs belong to another signer. Neither the device response
365
+ // nor concurrent code may replace their approved scripts or witnesses.
366
+ if (approvedFinalInputs.some((approved, index) => {
367
+ const current = finalizedFields(psbt.data.inputs[index]);
368
+ if (!approved)
369
+ return current !== null;
370
+ return (!current ||
371
+ current.scriptSig !== approved.scriptSig ||
372
+ current.witness !== approved.witness ||
373
+ Buffer.from(tx.ins[index].script).toString('hex') !==
374
+ approved.scriptSig ||
375
+ serializeWitness(tx.ins[index].witness || []).toString('hex') !==
376
+ approved.witness);
377
+ })) {
378
+ throw new Error('Trezor changed an already-finalized input');
379
+ }
380
+ for (const i of this.range(psbt.data.inputs.length)) {
381
+ if (approvedFinalInputs[i])
382
+ continue;
383
+ if (tx.ins[i].witness == null || tx.ins[i].witness.length !== 2) {
384
+ throw new Error('Please move your funds to a Segwit address: https://wiki.trezor.io/Account');
385
+ }
386
+ const partialSig = [
387
+ {
388
+ pubkey: tx.ins[i].witness[1],
389
+ signature: tx.ins[i].witness[0],
390
+ },
391
+ ];
392
+ psbt.updateInput(i, { partialSig });
393
+ }
394
+ try {
395
+ syscoinjs.utils.finalizePSBT(psbt);
396
+ }
397
+ catch (err) {
398
+ console.log(err);
399
+ }
400
+ return psbt;
401
+ }
402
+ setHdPath(coin, accountIndex, slip44) {
403
+ if (isEvmCoin(coin, slip44)) {
404
+ // For EVM, the "accountIndex" parameter is actually used as the address index
405
+ // EVM typically uses account 0, and different addresses are at different address indices
406
+ this.hdPath = getAddressDerivationPath(coin, slip44, 0, // account is always 0 for EVM
407
+ false, // not a change address
408
+ accountIndex // this is actually the address index for EVM
409
+ );
410
+ }
411
+ else {
412
+ // For UTXO, use account-level derivation path
413
+ this.hdPath = getAccountDerivationPath(coin, slip44, accountIndex);
414
+ }
415
+ }
416
+ convertToAddressNFormat(path) {
417
+ const pathArray = path.replace(/'/g, '').split('/');
418
+ pathArray.shift();
419
+ const addressN = [];
420
+ for (const index in pathArray) {
421
+ if (Number(index) <= 2 && Number(index) >= 0) {
422
+ addressN[Number(index)] = Number(pathArray[index]) | 0x80000000;
423
+ }
424
+ else {
425
+ addressN[Number(index)] = Number(pathArray[index]);
426
+ }
427
+ }
428
+ return addressN;
429
+ }
430
+ isScriptHash(address, networkInfo) {
431
+ if (!networkInfo) {
432
+ // If network info is not available, make a best guess based on address format
433
+ // This is a fallback for hardware wallet scenarios
434
+ if (this.isBech32(address)) {
435
+ const decoded = fromBech32(address);
436
+ return decoded.data.length === 32; // 32 bytes = P2WSH
437
+ }
438
+ // For non-bech32, we can't reliably determine without network info
439
+ // Default to false (P2PKH) as it's more common
440
+ return false;
441
+ }
442
+ if (!this.isBech32(address)) {
443
+ const decoded = fromBase58Check(address);
444
+ if (decoded.version === networkInfo.pubKeyHash) {
445
+ return false;
446
+ }
447
+ if (decoded.version === networkInfo.scriptHash) {
448
+ return true;
449
+ }
450
+ }
451
+ else {
452
+ const decoded = fromBech32(address);
453
+ if (decoded.data.length === 20) {
454
+ return false;
455
+ }
456
+ if (decoded.data.length === 32) {
457
+ return true;
458
+ }
459
+ }
460
+ throw new Error('isScriptHash: Unknown address type');
461
+ }
462
+ isPaymentFactory(payment) {
463
+ return (script) => {
464
+ try {
465
+ payment({ output: script });
466
+ return true;
467
+ }
468
+ catch (err) {
469
+ return false;
470
+ }
471
+ };
472
+ }
473
+ isBech32(address) {
474
+ try {
475
+ fromBech32(address);
476
+ return true;
477
+ }
478
+ catch (e) {
479
+ return false;
480
+ }
481
+ }
482
+ isP2WSHScript(script) {
483
+ this.isPaymentFactory(p2wsh)(script);
484
+ return false;
485
+ }
486
+ convertToTrezorFormat({ psbt, coin, network }) {
487
+ const trezortx = {};
488
+ trezortx.coin = coin;
489
+ trezortx.version = psbt.version;
490
+ trezortx.locktime = psbt.locktime;
491
+ trezortx.inputs = [];
492
+ trezortx.outputs = [];
493
+ for (let i = 0; i < psbt.txInputs.length; i++) {
494
+ const input = psbt.txInputs[i];
495
+ const inputItem = {};
496
+ inputItem.prev_index = input.index;
497
+ const hashBuf = Buffer.from(input.hash);
498
+ const prevHashHex = hashBuf.reverse().toString('hex'); // 64-char hex
499
+ inputItem.prev_hash = prevHashHex;
500
+ if (input.sequence !== undefined)
501
+ inputItem.sequence = input.sequence;
502
+ const dataInput = psbt.data.inputs[i];
503
+ if (dataInput.finalScriptSig || dataInput.finalScriptWitness) {
504
+ // A finalized co-signer input is supplied as public transaction data,
505
+ // never as a private derivation request to this device.
506
+ let prevout = dataInput.witnessUtxo;
507
+ if (dataInput.nonWitnessUtxo) {
508
+ const previous = bitcoinjs.Transaction.fromBuffer(Buffer.from(dataInput.nonWitnessUtxo));
509
+ const output = previous.outs[input.index];
510
+ if (!Buffer.from(previous.getHash()).equals(Buffer.from(input.hash)) ||
511
+ !output ||
512
+ (prevout &&
513
+ (!Buffer.from(prevout.script).equals(Buffer.from(output.script)) ||
514
+ prevout.value.toString() !== output.value.toString()))) {
515
+ throw new Error('Trezor external input has an invalid prevout');
516
+ }
517
+ prevout = output;
518
+ }
519
+ if (!prevout || prevout.value === undefined || !prevout.script) {
520
+ throw new Error('Trezor external input is missing its prevout');
521
+ }
522
+ inputItem.script_type = 'EXTERNAL';
523
+ inputItem.amount = prevout.value.toString();
524
+ inputItem.script_pubkey = Buffer.from(prevout.script).toString('hex');
525
+ if (dataInput.finalScriptSig)
526
+ inputItem.script_sig = Buffer.from(dataInput.finalScriptSig).toString('hex');
527
+ if (dataInput.finalScriptWitness)
528
+ inputItem.witness = Buffer.from(dataInput.finalScriptWitness).toString('hex');
529
+ trezortx.inputs.push(inputItem);
530
+ continue;
531
+ }
532
+ // Resolve derivation path for this input
533
+ let resolvedPath = null;
534
+ const tapDer = dataInput.tapBip32Derivation && dataInput.tapBip32Derivation.length > 0
535
+ ? dataInput.tapBip32Derivation[0]
536
+ : null;
537
+ const b32Der = dataInput.bip32Derivation && dataInput.bip32Derivation.length > 0
538
+ ? dataInput.bip32Derivation[0]
539
+ : null;
540
+ if (tapDer && tapDer.path) {
541
+ resolvedPath = tapDer.path;
542
+ }
543
+ else if (b32Der && b32Der.path) {
544
+ resolvedPath = b32Der.path;
545
+ }
546
+ else if (dataInput.unknownKeyVals && dataInput.unknownKeyVals.length) {
547
+ for (const kv of dataInput.unknownKeyVals) {
548
+ if (Buffer.isBuffer(kv.key) && kv.key.toString() === 'path') {
549
+ resolvedPath = kv.value.toString();
550
+ break;
551
+ }
552
+ }
553
+ }
554
+ if (!resolvedPath) {
555
+ throw new Error('convertToTrezorFormat: Missing path (tapBip32Derivation/bip32Derivation/unknownKeyVals)');
556
+ }
557
+ // Map path to Trezor address_n
558
+ inputItem.address_n = this.convertToAddressNFormat(resolvedPath);
559
+ // Coerce witnessUtxo.script to Buffer for downstream helpers
560
+ const inScriptBuf = dataInput && dataInput.witnessUtxo && dataInput.witnessUtxo.script
561
+ ? Buffer.isBuffer(dataInput.witnessUtxo.script)
562
+ ? dataInput.witnessUtxo.script
563
+ : Buffer.from(dataInput.witnessUtxo.script)
564
+ : Buffer.alloc(0);
565
+ // Select Trezor script_type (detect Taproot explicitly)
566
+ const isTaproot = inScriptBuf &&
567
+ inScriptBuf.length === 34 &&
568
+ inScriptBuf[0] === bitcoinops.OP_1 &&
569
+ inScriptBuf[1] === 0x20;
570
+ if (isTaproot) {
571
+ inputItem.script_type = 'SPENDTAPROOT';
572
+ }
573
+ else {
574
+ const scriptTypes = psbt.getInputType(i);
575
+ switch (scriptTypes) {
576
+ case 'multisig':
577
+ inputItem.script_type = 'SPENDMULTISIG';
578
+ break;
579
+ case 'witnesspubkeyhash':
580
+ inputItem.script_type = 'SPENDWITNESS';
581
+ break;
582
+ default:
583
+ inputItem.script_type = this.isP2WSHScript(inScriptBuf)
584
+ ? 'SPENDP2SHWITNESS'
585
+ : 'SPENDADDRESS';
586
+ break;
587
+ }
588
+ }
589
+ trezortx.inputs.push(inputItem);
590
+ }
591
+ for (let i = 0; i < psbt.txOutputs.length; i++) {
592
+ const output = psbt.txOutputs[i];
593
+ const outputItem = {};
594
+ const scriptBuf = Buffer.isBuffer(output.script)
595
+ ? output.script
596
+ : Buffer.from(output.script || []);
597
+ const chunks = decompile(scriptBuf);
598
+ // Debug logging to understand the output structure
599
+ console.log(`[Trezor] Processing output ${i}:`, {
600
+ value: output.value,
601
+ valueType: typeof output.value,
602
+ hasAddress: !!output.address,
603
+ address: output.address,
604
+ scriptLength: output.script?.length,
605
+ });
606
+ // Ensure value exists and convert properly
607
+ if (output.value === undefined || output.value === null) {
608
+ throw new Error(`Output ${i} has no value`);
609
+ }
610
+ // Handle different value types
611
+ let amountStr;
612
+ if (typeof output.value === 'bigint') {
613
+ amountStr = output.value.toString();
614
+ }
615
+ else if (typeof output.value === 'number') {
616
+ amountStr = output.value.toString();
617
+ }
618
+ else if (typeof output.value === 'string') {
619
+ amountStr = output.value;
620
+ }
621
+ else {
622
+ throw new Error(`Output ${i} has unexpected value type: ${typeof output.value}`);
623
+ }
624
+ outputItem.amount = amountStr;
625
+ if (chunks && chunks[0] === bitcoinops.OP_RETURN) {
626
+ outputItem.script_type = 'PAYTOOPRETURN';
627
+ // @ts-ignore
628
+ outputItem.op_return_data = Buffer.from(chunks[1]).toString('hex');
629
+ }
630
+ else {
631
+ if (output && this.isBech32(output.address)) {
632
+ // Any bech32 witness program (P2WPKH or P2WSH) signs as PAYTOWITNESS
633
+ outputItem.script_type = 'PAYTOWITNESS';
634
+ }
635
+ else {
636
+ outputItem.script_type = this.isScriptHash(output.address, network)
637
+ ? 'PAYTOSCRIPTHASH'
638
+ : 'PAYTOADDRESS';
639
+ }
640
+ if (output.address)
641
+ outputItem.address = output.address;
642
+ }
643
+ // Validate that the output has required fields for Trezor
644
+ if (outputItem.script_type === 'PAYTOADDRESS' ||
645
+ outputItem.script_type === 'PAYTOSCRIPTHASH' ||
646
+ outputItem.script_type === 'PAYTOWITNESS') {
647
+ if (!outputItem.address) {
648
+ console.error(`[Trezor] Output ${i} missing address for script type ${outputItem.script_type}`);
649
+ throw new Error(`Output ${i} requires an address for script type ${outputItem.script_type}`);
650
+ }
651
+ }
652
+ trezortx.outputs.push(outputItem);
653
+ }
654
+ return trezortx;
655
+ }
656
+ /**
657
+ * This sign EVM tx.
658
+ *
659
+ * @param index - index of account for path derivation
660
+ * @param tx - ethereum tx object
661
+ * @returns signature object
662
+ */
663
+ async signEthTransaction({ tx, index, coin, slip44, }) {
664
+ return this.executeWithRetry(async () => {
665
+ // Wait between popups
666
+ await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_POPUPS));
667
+ // Use dynamic path generation based on actual network parameters
668
+ this.setHdPath(coin, Number(index) || 0, slip44);
669
+ const response = await TrezorConnect.ethereumSignTransaction({
670
+ path: this.hdPath,
671
+ transaction: tx,
672
+ });
673
+ if (response.success) {
674
+ return response;
675
+ }
676
+ throw trezorResponseError(response);
677
+ }, 'signEthTransaction');
678
+ }
679
+ /**
680
+ * This sign message.
681
+ *
682
+ * @param coin - network symbol. Example: eth, sys, btc
683
+ * @param slip44 - network slip44 number
684
+ * @param message - message to be signed. Example: 'Test message'
685
+ * @param index - index of account for path derivation
686
+ * @returns signature object
687
+ */
688
+ async signMessage({ index, message, coin, slip44, address, }) {
689
+ return this.executeWithRetry(async () => {
690
+ // Wait between popups
691
+ await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_POPUPS));
692
+ if (isEvmCoin(coin, slip44) && `${index ? index : 0}` && message) {
693
+ return this._signEthPersonalMessage(Number(index), message, address);
694
+ }
695
+ return this._signUtxoPersonalMessage({ coin, index, slip44, message });
696
+ }, 'signMessage');
697
+ }
698
+ async _signUtxoPersonalMessage({ coin, index, slip44, message, }) {
699
+ try {
700
+ if (typeof message !== 'string')
701
+ throw new Error('Message must be a string');
702
+ // Use dynamic path generation instead of hardcoded switch
703
+ this.setHdPath(coin, index || 0, slip44);
704
+ const { success, payload } = await TrezorConnect.signMessage({
705
+ path: this.hdPath,
706
+ coin: coin,
707
+ message,
708
+ });
709
+ if (success) {
710
+ return { success, payload };
711
+ }
712
+ return { success: false, payload };
713
+ }
714
+ catch (error) {
715
+ return { error };
716
+ }
717
+ }
718
+ // For personal_sign, we need to prefix the message:
719
+ async _signEthPersonalMessage(index, message, address) {
720
+ return new Promise((resolve, reject) => {
721
+ setTimeout(async () => {
722
+ try {
723
+ this.setHdPath('eth', index, 60);
724
+ TrezorConnect.ethereumSignMessage({
725
+ path: this.hdPath,
726
+ message: stripHexPrefix(message),
727
+ hex: true,
728
+ })
729
+ .then((response) => {
730
+ if (response.success) {
731
+ if (address &&
732
+ response.payload.address.toLowerCase() !==
733
+ address.toLowerCase()) {
734
+ reject(new Error('signature doesnt match the right address'));
735
+ }
736
+ const signature = `0x${response.payload.signature}`;
737
+ resolve({ signature, success: true });
738
+ }
739
+ else {
740
+ reject(
741
+ // @ts-ignore
742
+ trezorResponseError(response));
743
+ }
744
+ })
745
+ .catch((e) => {
746
+ reject(e instanceof Error ? e : new Error(String(e)));
747
+ });
748
+ }
749
+ catch (error) {
750
+ reject(error);
751
+ }
752
+ // This is necessary to avoid popup collision
753
+ // between the unlock & sign trezor popups
754
+ }, DELAY_BETWEEN_POPUPS);
755
+ });
756
+ }
757
+ _sanitizeData(data) {
758
+ switch (Object.prototype.toString.call(data)) {
759
+ case '[object Object]': {
760
+ const entries = Object.keys(data).map((k) => [
761
+ k,
762
+ this._sanitizeData(data[k]),
763
+ ]);
764
+ return Object.fromEntries(entries);
765
+ }
766
+ case '[object Array]':
767
+ return data.map((v) => this._sanitizeData(v));
768
+ case '[object BigInt]':
769
+ return data.toString();
770
+ default:
771
+ return data;
772
+ }
773
+ }
774
+ /**
775
+ * EIP-712 Sign Typed Data
776
+ */
777
+ async signTypedData({ version, address, data, index, }) {
778
+ return this.executeWithRetry(async () => {
779
+ // Wait between popups
780
+ await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_POPUPS));
781
+ this.setHdPath('eth', index, 60);
782
+ // Use dynamic path generation for ETH (EVM) - typed data is only used for EVM
783
+ // V1 typed data is not supported by hardware wallets
784
+ if (version === SignTypedDataVersion.V1) {
785
+ throw new Error('Trezor: V1 typed data signing is not supported. Please use V3 or V4.');
786
+ }
787
+ const dataWithHashes = this._transformTypedData(data, version);
788
+ // set default values for signTypedData
789
+ // Trezor is stricter than @metamask/eth-sig-util in what it accepts
790
+ const { types, message = {}, domain = {}, primaryType,
791
+ // snake_case since Trezor uses Protobuf naming conventions here
792
+ domain_separator_hash, // eslint-disable-line camelcase
793
+ message_hash, // eslint-disable-line camelcase
794
+ } = dataWithHashes;
795
+ // This is necessary to avoid popup collision
796
+ // between the unlock & sign trezor popups
797
+ const response = await TrezorConnect.ethereumSignTypedData({
798
+ path: this.hdPath,
799
+ data: {
800
+ types: {
801
+ ...types,
802
+ EIP712Domain: types.EIP712Domain ? types.EIP712Domain : [],
803
+ },
804
+ message,
805
+ domain,
806
+ primaryType: primaryType,
807
+ },
808
+ metamask_v4_compat: version === SignTypedDataVersion.V4,
809
+ // Trezor 1 only supports blindly signing hashes
810
+ domain_separator_hash,
811
+ message_hash: message_hash ? message_hash : '',
812
+ });
813
+ if (response.success) {
814
+ if (address !== response.payload.address) {
815
+ throw new Error('signature doesnt match the right address');
816
+ }
817
+ return response.payload.signature;
818
+ }
819
+ // @ts-ignore
820
+ throw trezorResponseError(response);
821
+ }, 'signTypedData');
822
+ }
823
+ /**
824
+ * Verify UTXO address by displaying it on the Trezor device
825
+ * @param accountIndex - The account index
826
+ * @param currency - The currency (coin type)
827
+ * @param slip44 - The slip44 value for the network
828
+ * @returns The verified address
829
+ */
830
+ async verifyUtxoAddress(accountIndex, currency, slip44) {
831
+ return this.executeWithRetry(async () => {
832
+ const fullPath = getAddressDerivationPath(currency, slip44, accountIndex, false, // Not a change address
833
+ 0);
834
+ try {
835
+ const { payload, success } = await TrezorConnect.getAddress({
836
+ path: fullPath,
837
+ coin: currency,
838
+ showOnTrezor: true, // This displays the address on device for verification
839
+ });
840
+ if (success) {
841
+ return payload.address;
842
+ }
843
+ throw new Error('Address verification cancelled by user');
844
+ }
845
+ catch (error) {
846
+ throw error;
847
+ }
848
+ }, 'verifyUtxoAddress');
849
+ }
850
+ /**
851
+ * Check Trezor status
852
+ */
853
+ getStatus() {
854
+ return this.hardwareWalletManager
855
+ .getStatus()
856
+ .find((s) => s.type === HardwareWalletType.TREZOR);
857
+ }
858
+ /**
859
+ * Clean up resources
860
+ */
861
+ async destroy() {
862
+ this.unsubscribeTrezor?.();
863
+ this.unsubscribeTrezor = undefined;
864
+ try {
865
+ // First dispose Trezor Connect
866
+ this.dispose();
867
+ // Then destroy hardware wallet manager
868
+ await this.hardwareWalletManager.destroy();
869
+ this.initialized = false;
870
+ }
871
+ catch (error) {
872
+ console.error('Error destroying Trezor keyring:', error);
873
+ }
874
+ }
875
+ }
876
+ //# sourceMappingURL=index.js.map