@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.
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 +6 -2
  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,685 @@
1
+ /* eslint-disable camelcase */
2
+ /* eslint-disable import/no-named-as-default */
3
+ /* eslint-disable import/order */
4
+ import Transport from '@ledgerhq/hw-transport';
5
+ import SysUtxoClient, { DefaultWalletPolicy } from './bitcoin_client';
6
+ import { RECEIVING_ADDRESS_INDEX, WILL_NOT_DISPLAY } from './consts';
7
+ import { getNetworkConfig } from '@sidhujag/sysweb3-network';
8
+ import BIP32Factory from 'bip32';
9
+ import ecc from '@bitcoinerlab/secp256k1';
10
+ import { IEvmMethods, IUTXOMethods, MessageTypes } from './types';
11
+ import LedgerEthClient, { ledgerService } from '@ledgerhq/hw-app-eth';
12
+ import { Transaction } from 'syscoinjs-lib';
13
+ import {
14
+ TypedDataUtils,
15
+ TypedMessage,
16
+ SignTypedDataVersion,
17
+ TypedDataV1,
18
+ } from '@metamask/eth-sig-util';
19
+ import {
20
+ getAccountDerivationPath,
21
+ getAddressDerivationPath,
22
+ isEvmCoin,
23
+ convertExtendedKeyVersion,
24
+ } from '../utils/derivation-paths';
25
+
26
+ import {
27
+ HardwareWalletManager,
28
+ HardwareWalletType,
29
+ } from '../hardware-wallet-manager';
30
+ //
31
+
32
+ export class LedgerKeyring {
33
+ public ledgerEVMClient!: LedgerEthClient;
34
+ public ledgerUtxoClient!: SysUtxoClient;
35
+ private hdPath = "m/44'/57'/0'/0/0";
36
+ public evm: IEvmMethods;
37
+ public utxo: IUTXOMethods;
38
+ public transport: Transport | null = null;
39
+ private hardwareWalletManager: HardwareWalletManager;
40
+ // In-memory cache of registered wallet policy HMACs
41
+ private walletHmacCache: Map<string, Buffer> = new Map();
42
+
43
+ constructor() {
44
+ this.hardwareWalletManager = new HardwareWalletManager();
45
+
46
+ // Set up event listeners
47
+ this.hardwareWalletManager.on('connected', ({ type }) => {
48
+ if (type === HardwareWalletType.LEDGER) {
49
+ console.log('Ledger connected');
50
+ }
51
+ });
52
+
53
+ this.hardwareWalletManager.on('disconnected', ({ type }) => {
54
+ if (type === HardwareWalletType.LEDGER) {
55
+ console.log('Ledger disconnected');
56
+ this.transport = null;
57
+ // Clear clients on disconnect
58
+ this.ledgerEVMClient = null as any;
59
+ this.ledgerUtxoClient = null as any;
60
+ this.walletHmacCache.clear();
61
+ }
62
+ });
63
+
64
+ this.hardwareWalletManager.on('connectionFailed', ({ type, error }) => {
65
+ if (type === HardwareWalletType.LEDGER) {
66
+ console.error('Ledger connection failed:', error);
67
+ }
68
+ });
69
+
70
+ this.evm = {
71
+ getEvmAddressAndPubKey: this.getEvmAddressAndPubKey,
72
+ signEVMTransaction: this.signEVMTransaction,
73
+ signPersonalMessage: this.signPersonalMessage,
74
+ signTypedData: this.signTypedData,
75
+ };
76
+
77
+ this.utxo = {
78
+ getUtxoAddress: this.getUtxoAddress,
79
+ getXpub: this.getXpub,
80
+ verifyUtxoAddress: this.verifyUtxoAddress,
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Ensure Ledger is connected with automatic retry
86
+ * Note: This is automatically called by all operations through executeWithRetry
87
+ * External callers don't need to call this directly
88
+ */
89
+ public async ensureConnection(): Promise<void> {
90
+ await this.hardwareWalletManager.ensureConnection(
91
+ HardwareWalletType.LEDGER
92
+ );
93
+ this.transport = await this.hardwareWalletManager.getLedgerConnection();
94
+
95
+ // Create clients if transport is available
96
+ if (this.transport && (!this.ledgerEVMClient || !this.ledgerUtxoClient)) {
97
+ this.ledgerEVMClient = new LedgerEthClient(this.transport);
98
+ this.ledgerUtxoClient = new SysUtxoClient(this.transport);
99
+ }
100
+ }
101
+
102
+ private getUtxoAddress = async ({
103
+ coin,
104
+ index, // account index
105
+ slip44,
106
+ showInLedger,
107
+ }: {
108
+ coin: string;
109
+ index: number;
110
+ showInLedger?: boolean;
111
+ slip44: number;
112
+ }) => {
113
+ return this.executeWithRetry(async () => {
114
+ const fingerprint = await this.ledgerUtxoClient.getMasterFingerprint();
115
+ const xpub = await this.getXpub({ index, coin, slip44 });
116
+ this.setHdPath(coin, index, slip44);
117
+
118
+ // Convert stored/display zpub/vpub to device-friendly xpub/tpub for policy registration using network macros
119
+ const { types: deviceTypes } = getNetworkConfig(slip44, coin);
120
+ const devicePubMagicDec =
121
+ slip44 === 1
122
+ ? (deviceTypes.xPubType as any).testnet.vpub
123
+ : deviceTypes.xPubType.mainnet.zpub;
124
+ const devicePubMagicHex = Number(devicePubMagicDec)
125
+ .toString(16)
126
+ .padStart(8, '0');
127
+ const deviceXpub = convertExtendedKeyVersion(xpub, devicePubMagicHex);
128
+ const xpubWithDescriptor = `[${this.hdPath}]${deviceXpub}`.replace(
129
+ 'm',
130
+ fingerprint
131
+ );
132
+ const walletPolicy = new DefaultWalletPolicy(
133
+ // Ensure policy template matches BIP84 single-sig wpkh
134
+ 'wpkh(@0/**)',
135
+ xpubWithDescriptor
136
+ );
137
+
138
+ const hmac = await this.getOrRegisterHmac(walletPolicy, fingerprint);
139
+
140
+ const address = await this.ledgerUtxoClient.getWalletAddress(
141
+ walletPolicy,
142
+ hmac,
143
+ RECEIVING_ADDRESS_INDEX,
144
+ index,
145
+ !!showInLedger
146
+ );
147
+
148
+ return address;
149
+ }, 'getUtxoAddress');
150
+ };
151
+
152
+ public verifyUtxoAddress = async (
153
+ accountIndex: number,
154
+ currency: string,
155
+ slip44: number
156
+ ) =>
157
+ await this.getUtxoAddress({
158
+ coin: currency,
159
+ index: accountIndex,
160
+ slip44: slip44,
161
+ showInLedger: true,
162
+ });
163
+
164
+ private getXpub = async ({
165
+ index,
166
+ coin,
167
+ slip44,
168
+ }: {
169
+ coin: string;
170
+ index: number;
171
+ slip44: number;
172
+ }): Promise<string> => {
173
+ return this.executeWithRetry(async () => {
174
+ this.setHdPath(coin, index, slip44);
175
+
176
+ {
177
+ // Syscoin and general UTXO flow: try silent first, then fall back to on-device display for unusual paths
178
+ try {
179
+ const xpub = await this.ledgerUtxoClient.getExtendedPubkey(
180
+ this.hdPath,
181
+ WILL_NOT_DISPLAY
182
+ );
183
+ // Normalize to BIP84 prefix for Blockbook using network-config magic bytes
184
+ const { types } = getNetworkConfig(slip44, coin);
185
+ const target =
186
+ slip44 === 1
187
+ ? (types.zPubType as any).testnet.vpub
188
+ : types.zPubType.mainnet.zpub;
189
+ return convertExtendedKeyVersion(xpub, target);
190
+ } catch (err) {
191
+ // Retry with display=true to allow unusual paths with user approval
192
+ const xpubWithDisplay = await this.ledgerUtxoClient.getExtendedPubkey(
193
+ this.hdPath,
194
+ true
195
+ );
196
+ const { types } = getNetworkConfig(slip44, coin);
197
+ const target =
198
+ slip44 === 1
199
+ ? (types.zPubType as any).testnet.vpub
200
+ : types.zPubType.mainnet.zpub;
201
+ return convertExtendedKeyVersion(xpubWithDisplay, target);
202
+ }
203
+ }
204
+ }, 'getXpub');
205
+ };
206
+
207
+ /**
208
+ * Sign a UTXO message - public method used by transaction classes
209
+ */
210
+ public signUtxoMessage = async (path: string, message: string) => {
211
+ return this.executeWithRetry(async () => {
212
+ const bufferMessage = Buffer.from(message);
213
+ const signature = await this.ledgerUtxoClient.signMessage(
214
+ bufferMessage,
215
+ path
216
+ );
217
+ return signature;
218
+ }, 'signUtxoMessage');
219
+ };
220
+
221
+ private signEVMTransaction = async ({
222
+ rawTx,
223
+ accountIndex,
224
+ }: {
225
+ accountIndex: number;
226
+ rawTx: string;
227
+ }) => {
228
+ return this.executeWithRetry(async () => {
229
+ this.setHdPath('eth', accountIndex, 60);
230
+ const resolution = await ledgerService.resolveTransaction(rawTx, {}, {});
231
+
232
+ const signature = await this.ledgerEVMClient.signTransaction(
233
+ this.hdPath.replace(/^m\//, ''), // Remove 'm/' prefix for EVM
234
+ rawTx,
235
+ resolution
236
+ );
237
+
238
+ return signature;
239
+ }, 'signEVMTransaction');
240
+ };
241
+
242
+ private signPersonalMessage = async ({
243
+ message,
244
+ accountIndex,
245
+ }: {
246
+ accountIndex: number;
247
+ message: string;
248
+ }) => {
249
+ return this.executeWithRetry(async () => {
250
+ this.setHdPath('eth', accountIndex, 60);
251
+
252
+ const signature = await this.ledgerEVMClient.signPersonalMessage(
253
+ this.hdPath.replace(/^m\//, ''), // Remove 'm/' prefix for EVM
254
+ message
255
+ );
256
+
257
+ return `0x${signature.r}${signature.s}${signature.v.toString(16)}`;
258
+ }, 'signPersonalMessage');
259
+ };
260
+
261
+ private sanitizeData(data: any): any {
262
+ switch (Object.prototype.toString.call(data)) {
263
+ case '[object Object]': {
264
+ const entries = Object.keys(data).map((k) => [
265
+ k,
266
+ this.sanitizeData(data[k]),
267
+ ]);
268
+ return Object.fromEntries(entries);
269
+ }
270
+
271
+ case '[object Array]':
272
+ return data.map((v: any[]) => this.sanitizeData(v));
273
+
274
+ case '[object BigInt]':
275
+ return data.toString();
276
+
277
+ default:
278
+ return data;
279
+ }
280
+ }
281
+
282
+ private transformTypedData = <T extends MessageTypes>(
283
+ data: TypedMessage<T>,
284
+ version: SignTypedDataVersion
285
+ ) => {
286
+ const { types, primaryType, domain, message } = this.sanitizeData(data);
287
+
288
+ const domainSeparatorHash = TypedDataUtils.hashStruct(
289
+ 'EIP712Domain',
290
+ this.sanitizeData(domain),
291
+ types,
292
+ version as SignTypedDataVersion.V3 | SignTypedDataVersion.V4
293
+ ).toString('hex');
294
+
295
+ let messageHash: string | null = null;
296
+
297
+ if (primaryType !== 'EIP712Domain') {
298
+ messageHash = TypedDataUtils.hashStruct(
299
+ primaryType as string,
300
+ this.sanitizeData(message),
301
+ types,
302
+ version as SignTypedDataVersion.V3 | SignTypedDataVersion.V4
303
+ ).toString('hex');
304
+ }
305
+
306
+ return {
307
+ domain_separator_hash: domainSeparatorHash,
308
+ message_hash: messageHash,
309
+ ...data,
310
+ };
311
+ };
312
+
313
+ private getEvmAddressAndPubKey = async ({
314
+ accountIndex,
315
+ }: {
316
+ accountIndex: number;
317
+ }): Promise<{ address: string; publicKey: string }> => {
318
+ return this.executeWithRetry(async () => {
319
+ this.setHdPath('eth', accountIndex, 60);
320
+ const { address, publicKey } = await this.ledgerEVMClient.getAddress(
321
+ this.hdPath.replace(/^m\//, '') // Remove 'm/' prefix for EVM
322
+ );
323
+ return { address, publicKey };
324
+ }, 'getEvmAddressAndPubKey');
325
+ };
326
+
327
+ private signTypedData = async ({
328
+ version,
329
+ data,
330
+ accountIndex,
331
+ }: {
332
+ accountIndex: number;
333
+ data: TypedMessage<any> | TypedDataV1;
334
+ version: SignTypedDataVersion;
335
+ }) => {
336
+ return this.executeWithRetry(async () => {
337
+ this.setHdPath('eth', accountIndex, 60);
338
+
339
+ // V1 typed data is not supported by hardware wallets
340
+ if (version === SignTypedDataVersion.V1) {
341
+ throw new Error(
342
+ 'Ledger: V1 typed data signing is not supported. Please use V3 or V4.'
343
+ );
344
+ }
345
+
346
+ const dataWithHashes = this.transformTypedData(
347
+ data as TypedMessage<any>,
348
+ version
349
+ );
350
+
351
+ const { domain_separator_hash, message_hash } = dataWithHashes;
352
+
353
+ const signature = await this.ledgerEVMClient.signEIP712HashedMessage(
354
+ this.hdPath.replace(/^m\//, ''), // Remove 'm/' prefix for EVM
355
+ domain_separator_hash,
356
+ message_hash ? message_hash : ''
357
+ );
358
+
359
+ return `0x${signature.r}${signature.s}${signature.v.toString(16)}`;
360
+ }, 'signTypedData');
361
+ };
362
+
363
+ private getMasterFingerprint = async () => {
364
+ try {
365
+ const masterFingerprint =
366
+ await this.ledgerUtxoClient.getMasterFingerprint();
367
+ return masterFingerprint;
368
+ } catch (error) {
369
+ console.log('Fingerprint error: ', error);
370
+ throw error;
371
+ }
372
+ };
373
+
374
+ // Build a stable cache key for a policy bound to the device and derivation path
375
+ private buildWalletCacheKey(
376
+ fingerprint: string,
377
+ descriptorTemplate: string,
378
+ hdPath: string
379
+ ): string {
380
+ return `${fingerprint}|${descriptorTemplate}|${hdPath}`;
381
+ }
382
+
383
+ // Lazily register the wallet policy and cache HMAC in memory only
384
+ private async getOrRegisterHmac(
385
+ walletPolicy: any,
386
+ fingerprint: string
387
+ ): Promise<Buffer | null> {
388
+ const cacheKey = this.buildWalletCacheKey(
389
+ fingerprint,
390
+ walletPolicy.descriptorTemplate,
391
+ this.hdPath
392
+ );
393
+
394
+ const cached = this.walletHmacCache.get(cacheKey);
395
+ if (cached) return cached;
396
+
397
+ // If registerWallet is unavailable (tests/mocks), fall back to null HMAC
398
+ const registerWallet: any = (this.ledgerUtxoClient as any)?.registerWallet;
399
+ if (typeof registerWallet !== 'function') {
400
+ return null;
401
+ }
402
+
403
+ try {
404
+ // Register once (device approval). If user cancels, error will propagate via retryOperation
405
+ const result = await registerWallet.call(
406
+ this.ledgerUtxoClient,
407
+ walletPolicy
408
+ );
409
+ const walletHMAC = Array.isArray(result) ? result[1] : null;
410
+ if (walletHMAC && Buffer.isBuffer(walletHMAC)) {
411
+ this.walletHmacCache.set(cacheKey, walletHMAC);
412
+ return walletHMAC;
413
+ }
414
+ return null;
415
+ } catch (e) {
416
+ // On failure, proceed without HMAC (device may prompt)
417
+ return null;
418
+ }
419
+ }
420
+
421
+ private setHdPath(coin: string, accountIndex: number, slip44: number) {
422
+ if (isEvmCoin(coin, slip44)) {
423
+ // For EVM, the "accountIndex" parameter is actually used as the address index
424
+ // EVM typically uses account 0, and different addresses are at different address indices
425
+ this.hdPath = getAddressDerivationPath(
426
+ coin,
427
+ slip44,
428
+ 0, // account is always 0 for EVM
429
+ false, // not a change address
430
+ accountIndex // this is actually the address index for EVM
431
+ );
432
+ } else {
433
+ // For UTXO, use account-level derivation path
434
+ this.hdPath = getAccountDerivationPath(coin, slip44, accountIndex);
435
+ }
436
+ }
437
+ /**
438
+ * Convert PSBT to Ledger format with retry logic
439
+ */
440
+ public async convertToLedgerFormat(
441
+ psbt: any,
442
+ accountXpub: string,
443
+ accountId: number,
444
+ currency: string,
445
+ slip44: number
446
+ ): Promise<any> {
447
+ return this.executeWithRetry(async () => {
448
+ // Ensure Ledger is connected before attempting operations
449
+ // This is now handled by executeWithRetry
450
+
451
+ // Build a bitcoinjs-bip32 network from slip44/currency so zpub/vpub parse directly
452
+ const { networks, types } = getNetworkConfig(slip44, currency);
453
+ const isTestnet = slip44 === 1;
454
+ const pubTypes = isTestnet
455
+ ? (types.zPubType as any).testnet
456
+ : types.zPubType.mainnet;
457
+ const baseNetwork = isTestnet ? networks.testnet : networks.mainnet;
458
+ const network = {
459
+ ...baseNetwork,
460
+ bip32: {
461
+ public: parseInt(pubTypes.vpub || pubTypes.zpub, 16),
462
+ private: parseInt(pubTypes.vprv || pubTypes.zprv, 16),
463
+ },
464
+ } as any;
465
+
466
+ const bip32 = BIP32Factory(ecc as any);
467
+ const accountNode = bip32.fromBase58(accountXpub, network);
468
+
469
+ // Get master fingerprint
470
+ const fingerprint = await this.getMasterFingerprint();
471
+
472
+ // Enhance each input with bip32Derivation
473
+ const missingInputDerivations: number[] = [];
474
+ for (let i = 0; i < psbt.inputCount; i++) {
475
+ const dataInput = psbt.data.inputs[i];
476
+
477
+ // Skip if already has bip32Derivation
478
+ if (dataInput.bip32Derivation && dataInput.bip32Derivation.length > 0) {
479
+ continue;
480
+ }
481
+
482
+ // Ensure witnessUtxo is present if nonWitnessUtxo exists
483
+ if (!dataInput.witnessUtxo && dataInput.nonWitnessUtxo) {
484
+ const txBuffer = dataInput.nonWitnessUtxo;
485
+ const tx = Transaction.fromBuffer(txBuffer);
486
+ const vout = psbt.txInputs[i].index;
487
+
488
+ if (tx.outs[vout]) {
489
+ dataInput.witnessUtxo = {
490
+ script: tx.outs[vout].script,
491
+ value: tx.outs[vout].value,
492
+ };
493
+ }
494
+ }
495
+
496
+ // Extract path from unknownKeyVals by searching for the key, not using hardcoded index
497
+ let pathFromInput: string | null = null;
498
+ if (dataInput.unknownKeyVals && dataInput.unknownKeyVals.length > 0) {
499
+ for (const kv of dataInput.unknownKeyVals) {
500
+ if (kv.key.equals(Buffer.from('path'))) {
501
+ pathFromInput = kv.value.toString();
502
+ break;
503
+ }
504
+ }
505
+ }
506
+
507
+ if (pathFromInput) {
508
+ const fullPath = pathFromInput;
509
+ const accountPath = getAccountDerivationPath(
510
+ currency,
511
+ slip44,
512
+ accountId
513
+ );
514
+ const relativePath = fullPath
515
+ .replace(accountPath, '')
516
+ .replace(/^\//, '');
517
+ const derivationTokens = relativePath.split('/').filter((t) => t);
518
+
519
+ const derivedAccount = derivationTokens.reduce(
520
+ (acc: any, token: string) => {
521
+ const index = parseInt(token);
522
+ if (isNaN(index)) {
523
+ return acc;
524
+ }
525
+ return acc.derive(index);
526
+ },
527
+ accountNode
528
+ );
529
+
530
+ const pubkey = derivedAccount.publicKey;
531
+
532
+ if (pubkey && Buffer.isBuffer(pubkey)) {
533
+ // Add the bip32Derivation that Ledger needs
534
+ const bip32Derivation = {
535
+ masterFingerprint: Buffer.from(fingerprint, 'hex'),
536
+ path: fullPath,
537
+ pubkey: pubkey,
538
+ };
539
+
540
+ psbt.updateInput(i, {
541
+ bip32Derivation: [bip32Derivation],
542
+ });
543
+ }
544
+ }
545
+
546
+ if (
547
+ !dataInput.bip32Derivation ||
548
+ dataInput.bip32Derivation.length === 0
549
+ ) {
550
+ missingInputDerivations.push(i);
551
+ }
552
+ }
553
+
554
+ // Enhance each output with bip32Derivation when it's a change/output owned by the wallet
555
+ const missingOutputDerivations: number[] = [];
556
+ for (let i = 0; i < psbt.data.outputs.length; i++) {
557
+ const dataOutput = psbt.data.outputs[i];
558
+
559
+ // Skip if derivation already present
560
+ if (
561
+ dataOutput.bip32Derivation &&
562
+ dataOutput.bip32Derivation.length > 0
563
+ ) {
564
+ continue;
565
+ }
566
+
567
+ // Extract path from unknownKeyVals by searching for the key 'path'
568
+ let pathFromOutput: string | null = null;
569
+ if (dataOutput.unknownKeyVals && dataOutput.unknownKeyVals.length > 0) {
570
+ for (const kv of dataOutput.unknownKeyVals) {
571
+ if (kv.key.equals(Buffer.from('path'))) {
572
+ pathFromOutput = kv.value.toString();
573
+ break;
574
+ }
575
+ }
576
+ }
577
+
578
+ if (pathFromOutput) {
579
+ const fullPath = pathFromOutput;
580
+ const accountPath = getAccountDerivationPath(
581
+ currency,
582
+ slip44,
583
+ accountId
584
+ );
585
+ const relativePath = fullPath
586
+ .replace(accountPath, '')
587
+ .replace(/^\//, '');
588
+ const derivationTokens = relativePath.split('/').filter((t) => t);
589
+
590
+ const derivedAccount = derivationTokens.reduce(
591
+ (acc: any, token: string) => {
592
+ const index = parseInt(token);
593
+ if (isNaN(index)) {
594
+ return acc;
595
+ }
596
+ return acc.derive(index);
597
+ },
598
+ accountNode
599
+ );
600
+
601
+ const pubkey = derivedAccount.publicKey;
602
+
603
+ if (pubkey && Buffer.isBuffer(pubkey)) {
604
+ const bip32Derivation = {
605
+ masterFingerprint: Buffer.from(fingerprint, 'hex'),
606
+ path: fullPath,
607
+ pubkey: pubkey,
608
+ };
609
+
610
+ psbt.updateOutput(i, {
611
+ bip32Derivation: [bip32Derivation],
612
+ });
613
+ }
614
+ }
615
+
616
+ // Track outputs that declared a path but still lack derivation info
617
+ if (pathFromOutput) {
618
+ if (
619
+ !dataOutput.bip32Derivation ||
620
+ dataOutput.bip32Derivation.length === 0
621
+ ) {
622
+ missingOutputDerivations.push(i);
623
+ }
624
+ }
625
+ }
626
+
627
+ // If any wallet-owned inputs/outputs are missing bip32Derivation, fail early with a clear error
628
+ if (
629
+ missingInputDerivations.length > 0 ||
630
+ missingOutputDerivations.length > 0
631
+ ) {
632
+ const parts: string[] = [];
633
+ if (missingInputDerivations.length > 0) {
634
+ parts.push(`inputs [${missingInputDerivations.join(', ')}]`);
635
+ }
636
+ if (missingOutputDerivations.length > 0) {
637
+ parts.push(`outputs [${missingOutputDerivations.join(', ')}]`);
638
+ }
639
+ throw new Error(
640
+ `convertToLedgerFormat: Missing bip32Derivation for ${parts.join(
641
+ ' and '
642
+ )}. Ensure PSBT includes a 'path' unknownKeyVal or BIP32_DERIVATION for wallet-owned entries.`
643
+ );
644
+ }
645
+
646
+ return psbt;
647
+ }, 'convertToLedgerFormat');
648
+ }
649
+
650
+ /**
651
+ * Execute operation with automatic retry
652
+ */
653
+ private async executeWithRetry<T>(
654
+ operation: () => Promise<T>,
655
+ operationName: string
656
+ ): Promise<T> {
657
+ // Ensure connection first
658
+ await this.ensureConnection();
659
+
660
+ // Use hardware wallet manager's retry mechanism
661
+ return this.hardwareWalletManager.retryOperation(operation, operationName, {
662
+ maxRetries: 3,
663
+ baseDelay: 1000,
664
+ maxDelay: 5000,
665
+ backoffMultiplier: 2,
666
+ });
667
+ }
668
+
669
+ /**
670
+ * Get hardware wallet status
671
+ */
672
+ public getStatus() {
673
+ return this.hardwareWalletManager
674
+ .getStatus()
675
+ .find((s) => s.type === HardwareWalletType.LEDGER);
676
+ }
677
+
678
+ /**
679
+ * Clean up resources
680
+ */
681
+ public async destroy() {
682
+ await this.hardwareWalletManager.destroy();
683
+ this.transport = null;
684
+ }
685
+ }