@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,869 @@
1
+ import { INetworkType } from '@sidhujag/sysweb3-network';
2
+
3
+ import { KeyringManager, KeyringAccountType } from '../../../src';
4
+ import { FAKE_PASSWORD, PEACE_SEED_PHRASE } from '../../helpers/constants';
5
+ import { setupMocks } from '../../helpers/setup';
6
+
7
+ // Use global createMockVaultState
8
+ const createMockVaultState = (global as any).createMockVaultState;
9
+
10
+ describe('Ledger Hardware Wallet', () => {
11
+ let keyringManager: KeyringManager;
12
+ let mockVaultStateGetter: jest.Mock;
13
+ let currentVaultState: any;
14
+
15
+ beforeEach(async () => {
16
+ setupMocks();
17
+ await global.setupTestVault(FAKE_PASSWORD);
18
+
19
+ // Set up UTXO vault state
20
+ currentVaultState = createMockVaultState({
21
+ activeAccountId: 0,
22
+ activeAccountType: KeyringAccountType.HDAccount,
23
+ networkType: INetworkType.Syscoin,
24
+ chainId: 57,
25
+ });
26
+ mockVaultStateGetter = jest.fn(() => currentVaultState);
27
+
28
+ keyringManager = await KeyringManager.createInitialized(
29
+ PEACE_SEED_PHRASE,
30
+ FAKE_PASSWORD,
31
+ mockVaultStateGetter
32
+ );
33
+
34
+ // Mock HardwareWalletManager ensureConnection
35
+ keyringManager.ledgerSigner.ensureConnection = jest
36
+ .fn()
37
+ .mockResolvedValue(undefined);
38
+
39
+ // Mock transport property
40
+ keyringManager.ledgerSigner.transport = {} as any;
41
+
42
+ // Mock ledgerUtxoClient for getMasterFingerprint calls
43
+ keyringManager.ledgerSigner.ledgerUtxoClient = {
44
+ getMasterFingerprint: jest.fn().mockResolvedValue('12345678'),
45
+ signPsbt: jest.fn(),
46
+ } as any;
47
+
48
+ // Mock getAddress to return our mocked addresses
49
+ keyringManager.getAddress = jest
50
+ .fn()
51
+ .mockResolvedValue('sys1qmock_ledger_address');
52
+ });
53
+
54
+ afterEach(async () => {
55
+ // Clean up hardware wallet connections
56
+ if (keyringManager) {
57
+ await keyringManager.destroy();
58
+ }
59
+ });
60
+
61
+ describe('Account Import', () => {
62
+ it('should import Ledger account when connected', async () => {
63
+ // Mock complete UTXO interface
64
+ keyringManager.ledgerSigner.utxo = {
65
+ getXpub: jest
66
+ .fn()
67
+ .mockResolvedValue(
68
+ 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs'
69
+ ),
70
+ getUtxoAddress: jest.fn().mockResolvedValue('sys1qmock_ledger_address'),
71
+ verifyUtxoAddress: jest
72
+ .fn()
73
+ .mockResolvedValue('sys1qmock_ledger_address'),
74
+ };
75
+
76
+ const account = await keyringManager.importLedgerAccount('My Ledger');
77
+
78
+ expect(account).toBeDefined();
79
+ if (account) {
80
+ expect(account.id).toBe(0); // First imported Ledger account gets ID 0
81
+ expect(account.label).toBe('My Ledger');
82
+ expect(account.isLedgerWallet).toBe(true);
83
+ expect(account.isTrezorWallet).toBe(false);
84
+ expect(account.isImported).toBe(false);
85
+ expect(account.xprv).toBe(''); // Hardware wallets don't expose private keys
86
+ expect(account.address).toBe('sys1qmock_ledger_address');
87
+ }
88
+ });
89
+
90
+ it('should fail if Ledger connection fails', async () => {
91
+ // Mock connection failure in ensureConnection
92
+ keyringManager.ledgerSigner.ensureConnection = jest
93
+ .fn()
94
+ .mockRejectedValue(new Error('Failed to connect to device'));
95
+
96
+ await expect(keyringManager.importLedgerAccount()).rejects.toThrow(
97
+ 'Failed to connect to device'
98
+ );
99
+ });
100
+
101
+ it('should handle Ledger import for EVM networks', async () => {
102
+ // Set up EVM vault state
103
+ const evmVaultState = createMockVaultState({
104
+ activeAccountId: 0,
105
+ activeAccountType: KeyringAccountType.HDAccount,
106
+ networkType: INetworkType.Ethereum,
107
+ chainId: 1,
108
+ });
109
+ const evmVaultStateGetter = jest.fn(() => evmVaultState);
110
+
111
+ // Create EVM keyring
112
+ const evmKeyring = await KeyringManager.createInitialized(
113
+ PEACE_SEED_PHRASE,
114
+ FAKE_PASSWORD,
115
+ evmVaultStateGetter
116
+ );
117
+
118
+ // Mock ensureConnection for EVM keyring
119
+ evmKeyring.ledgerSigner.ensureConnection = jest
120
+ .fn()
121
+ .mockResolvedValue(undefined);
122
+
123
+ // Mock complete EVM interface
124
+ evmKeyring.ledgerSigner.evm = {
125
+ getEvmAddressAndPubKey: jest.fn().mockResolvedValue({
126
+ address: '0x742d35Cc6634C0532925a3b844Bc9e7595f2bd9F',
127
+ publicKey: '0x04...',
128
+ }),
129
+ signEVMTransaction: jest.fn().mockResolvedValue({
130
+ r: '123456789abcdef123456789abcdef123456789abcdef123456789abcdef12345678',
131
+ s: '987654321fedcba987654321fedcba987654321fedcba987654321fedcba987654',
132
+ v: '1c',
133
+ }),
134
+ signPersonalMessage: jest.fn().mockResolvedValue('0xmocked_signature'),
135
+ signTypedData: jest.fn().mockResolvedValue('0xmocked_typed_signature'),
136
+ };
137
+
138
+ const account = await evmKeyring.importLedgerAccount('Ledger ETH');
139
+
140
+ expect(account).toBeDefined();
141
+ if (account) {
142
+ expect(account.address.startsWith('0x')).toBe(true);
143
+ expect(account.isLedgerWallet).toBe(true);
144
+ }
145
+ });
146
+
147
+ it('should reject duplicate Ledger addresses', async () => {
148
+ // Mock getAddress to return the expected addresses
149
+ keyringManager.getAddress = jest
150
+ .fn()
151
+ .mockResolvedValue('sys1q_first_address');
152
+
153
+ // First import with unique address
154
+ keyringManager.ledgerSigner.utxo = {
155
+ getXpub: jest
156
+ .fn()
157
+ .mockResolvedValue(
158
+ 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs'
159
+ ),
160
+ getUtxoAddress: jest.fn().mockResolvedValue('sys1q_first_address'),
161
+ verifyUtxoAddress: jest.fn().mockResolvedValue('sys1q_first_address'),
162
+ };
163
+
164
+ const firstAccount = await keyringManager.importLedgerAccount();
165
+
166
+ // Update vault state with first account
167
+ if (firstAccount) {
168
+ currentVaultState.accounts[KeyringAccountType.Ledger][firstAccount.id] =
169
+ {
170
+ id: firstAccount.id,
171
+ label: firstAccount.label,
172
+ address: firstAccount.address,
173
+ xpub: firstAccount.xpub,
174
+ xprv: '',
175
+ isImported: false,
176
+ isTrezorWallet: false,
177
+ isLedgerWallet: true,
178
+ balances: { syscoin: 0, ethereum: 0 },
179
+ assets: { syscoin: [], ethereum: [] },
180
+ };
181
+ }
182
+
183
+ // Mock Ledger returning same address for next account (simulate duplicate)
184
+ keyringManager.ledgerSigner.utxo = {
185
+ getXpub: jest
186
+ .fn()
187
+ .mockResolvedValue(
188
+ 'zpub6s8HtEQtcu3AmBn9sniSqCAVhx2nJAhb2sd5NDYeYZ1ZJaZx7MAVZZnG1PdCUNJcVJXGbVpGfSYZLgkPSUjLYnJg8UdYvdkfaygcXZKPLy6'
189
+ ),
190
+ getUtxoAddress: jest.fn().mockResolvedValue('sys1q_first_address'), // Same as first!
191
+ verifyUtxoAddress: jest.fn().mockResolvedValue('sys1q_first_address'),
192
+ };
193
+
194
+ // Should reject duplicate - this tests the actual business logic
195
+ await expect(keyringManager.importLedgerAccount()).rejects.toThrow(
196
+ 'Account already exists'
197
+ );
198
+ });
199
+ });
200
+
201
+ describe('Account Management', () => {
202
+ beforeEach(async () => {
203
+ // Mock complete Ledger methods
204
+ keyringManager.ledgerSigner.utxo = {
205
+ getXpub: jest
206
+ .fn()
207
+ .mockResolvedValue(
208
+ 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs'
209
+ ),
210
+ getUtxoAddress: jest.fn().mockResolvedValue('sys1qmock_ledger_address'),
211
+ verifyUtxoAddress: jest
212
+ .fn()
213
+ .mockResolvedValue('sys1qmock_ledger_address'),
214
+ };
215
+
216
+ // Import a Ledger account
217
+ const account = await keyringManager.importLedgerAccount('Test Ledger');
218
+
219
+ // Update vault state with imported account
220
+ if (account) {
221
+ currentVaultState.accounts[KeyringAccountType.Ledger][account.id] = {
222
+ id: account.id,
223
+ label: account.label,
224
+ address: account.address,
225
+ xpub: account.xpub,
226
+ xprv: '',
227
+ isImported: false,
228
+ isTrezorWallet: false,
229
+ isLedgerWallet: true,
230
+ balances: { syscoin: 0, ethereum: 0 },
231
+ assets: { syscoin: [], ethereum: [] },
232
+ };
233
+ }
234
+ });
235
+
236
+ it('should switch to Ledger account', async () => {
237
+ // Update vault state to set Ledger as active
238
+ currentVaultState.activeAccount = {
239
+ id: 0,
240
+ type: KeyringAccountType.Ledger,
241
+ };
242
+
243
+ // Account switch is handled by vault state update
244
+
245
+ const { activeAccount, activeAccountType } =
246
+ keyringManager.getActiveAccount();
247
+ expect(activeAccountType).toBe(KeyringAccountType.Ledger);
248
+ expect(activeAccount.isLedgerWallet).toBe(true);
249
+ expect(activeAccount.label).toBe('Test Ledger');
250
+ });
251
+
252
+ it('should get Ledger account xpub', async () => {
253
+ // Update vault state to set Ledger as active
254
+ currentVaultState.activeAccount = {
255
+ id: 0,
256
+ type: KeyringAccountType.Ledger,
257
+ };
258
+
259
+ // Account switch is handled by vault state update
260
+
261
+ const xpub = keyringManager.getAccountXpub();
262
+ expect(xpub).toBeDefined();
263
+ expect(xpub).toBe(
264
+ 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs'
265
+ );
266
+ });
267
+
268
+ it('should handle mixed account types with Ledger', async () => {
269
+ // Add HD account
270
+ const hdAccount = await keyringManager.addNewAccount('HD Account 2');
271
+
272
+ // Update vault state with HD account
273
+ currentVaultState.accounts[KeyringAccountType.HDAccount][hdAccount.id] = {
274
+ id: hdAccount.id,
275
+ label: hdAccount.label,
276
+ address: hdAccount.address,
277
+ xpub: hdAccount.xpub,
278
+ xprv: '',
279
+ isImported: false,
280
+ isTrezorWallet: false,
281
+ isLedgerWallet: false,
282
+ balances: { syscoin: 0, ethereum: 0 },
283
+ assets: { syscoin: [], ethereum: [] },
284
+ };
285
+
286
+ // Switch between them using actual account IDs
287
+ currentVaultState.activeAccount = {
288
+ id: 0,
289
+ type: KeyringAccountType.HDAccount,
290
+ };
291
+ // Account switch is handled by vault state update
292
+ expect(keyringManager.getActiveAccount().activeAccountType).toBe(
293
+ KeyringAccountType.HDAccount
294
+ );
295
+
296
+ // Switch to Ledger account
297
+ currentVaultState.activeAccount = {
298
+ id: 0,
299
+ type: KeyringAccountType.Ledger,
300
+ };
301
+ // Account switch is handled by vault state update
302
+ expect(keyringManager.getActiveAccount().activeAccountType).toBe(
303
+ KeyringAccountType.Ledger
304
+ );
305
+ });
306
+ });
307
+
308
+ describe('Transaction Signing', () => {
309
+ beforeEach(async () => {
310
+ // Complete mock setup for transaction signing tests
311
+ keyringManager.ledgerSigner.utxo = {
312
+ getXpub: jest
313
+ .fn()
314
+ .mockResolvedValue(
315
+ 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs'
316
+ ),
317
+ getUtxoAddress: jest.fn().mockResolvedValue('sys1qmock_ledger_address'),
318
+ verifyUtxoAddress: jest
319
+ .fn()
320
+ .mockResolvedValue('sys1qmock_ledger_address'),
321
+ };
322
+
323
+ const account = await keyringManager.importLedgerAccount('Signing Test');
324
+ if (account) {
325
+ // Update vault state to set Ledger as active
326
+ currentVaultState.accounts[KeyringAccountType.Ledger][account.id] = {
327
+ id: account.id,
328
+ label: account.label,
329
+ address: account.address,
330
+ xpub: account.xpub,
331
+ xprv: '',
332
+ isImported: false,
333
+ isTrezorWallet: false,
334
+ isLedgerWallet: true,
335
+ balances: { syscoin: 0, ethereum: 0 },
336
+ assets: { syscoin: [], ethereum: [] },
337
+ };
338
+ currentVaultState.activeAccount = {
339
+ id: account.id,
340
+ type: KeyringAccountType.Ledger,
341
+ };
342
+
343
+ // Account switch is handled by vault state update
344
+ }
345
+
346
+ // Mock getAddress to return our mocked addresses
347
+ keyringManager.getAddress = jest
348
+ .fn()
349
+ .mockResolvedValue('sys1qmock_ledger_address');
350
+
351
+ // Mock Ledger UTXO client for transaction signing
352
+ keyringManager.ledgerSigner.ledgerUtxoClient = {
353
+ getMasterFingerprint: jest.fn().mockResolvedValue('12345678'),
354
+ signPsbt: jest.fn().mockResolvedValue([
355
+ [
356
+ 0,
357
+ {
358
+ pubkey: Buffer.from('mock_pubkey'),
359
+ signature: Buffer.from('mock_signature'),
360
+ },
361
+ ],
362
+ ]),
363
+ // Add minimal required properties to satisfy interface
364
+ transport: {} as any,
365
+ makeRequest: jest.fn(),
366
+ getAppAndVersion: jest.fn(),
367
+ getExtendedPubkey: jest.fn(),
368
+ registerWallet: jest.fn(),
369
+ getWalletAddress: jest.fn(),
370
+ signMessage: jest.fn(),
371
+ } as any;
372
+
373
+ // Mock convertToLedgerFormat to return a proper PSBT mock with toBase64 method
374
+ keyringManager.ledgerSigner.convertToLedgerFormat = jest
375
+ .fn()
376
+ .mockResolvedValue({
377
+ toBase64: jest
378
+ .fn()
379
+ .mockReturnValue(
380
+ 'cHNidP8BAHECAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='
381
+ ),
382
+ extractTransaction: jest.fn().mockReturnValue({
383
+ getId: jest.fn().mockReturnValue('mock_transaction_id'),
384
+ }),
385
+ updateInput: jest.fn(),
386
+ finalizeAllInputs: jest.fn(),
387
+ });
388
+ });
389
+
390
+ it('should prepare PSBT for Ledger signing', async () => {
391
+ // Mock the PSBT parsing to avoid base64 validation issues
392
+ const mockSyscoinjs = require('syscoinjs-lib');
393
+ const originalImportPsbtFromJson = mockSyscoinjs.utils.importPsbtFromJson;
394
+ mockSyscoinjs.utils.importPsbtFromJson = jest.fn().mockReturnValue({
395
+ psbt: {
396
+ toBase64: jest
397
+ .fn()
398
+ .mockReturnValue(
399
+ 'cHNidP8BAHECAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='
400
+ ),
401
+ extractTransaction: jest.fn().mockReturnValue({
402
+ getId: jest.fn().mockReturnValue('mock_transaction_id'),
403
+ }),
404
+ updateInput: jest.fn(),
405
+ finalizeAllInputs: jest.fn(),
406
+ },
407
+ });
408
+
409
+ const psbtData = {
410
+ psbt: 'valid_psbt_data',
411
+ assets: [],
412
+ };
413
+
414
+ const result = await keyringManager.syscoinTransaction.signPSBT({
415
+ psbt: psbtData,
416
+ isTrezor: false,
417
+ isLedger: true,
418
+ });
419
+
420
+ expect(result).toBeDefined();
421
+ // Verify Ledger-specific handling was applied
422
+ // Restore original function
423
+ mockSyscoinjs.utils.importPsbtFromJson = originalImportPsbtFromJson;
424
+ });
425
+
426
+ it('should reconnect to Ledger when connection is lost during EVM transaction signing', async () => {
427
+ // Set up EVM vault state
428
+ const evmVaultState = createMockVaultState({
429
+ activeAccountId: 0,
430
+ activeAccountType: KeyringAccountType.Ledger,
431
+ networkType: INetworkType.Ethereum,
432
+ chainId: 1,
433
+ });
434
+ const evmVaultStateGetter = jest.fn(() => evmVaultState);
435
+
436
+ // Create EVM keyring with Ledger
437
+ const evmKeyring = await KeyringManager.createInitialized(
438
+ PEACE_SEED_PHRASE,
439
+ FAKE_PASSWORD,
440
+ evmVaultStateGetter
441
+ );
442
+
443
+ // Mock Ledger as disconnected
444
+ evmKeyring.ledgerSigner.transport = null;
445
+ evmKeyring.ledgerSigner.ledgerUtxoClient = null as any;
446
+ evmKeyring.ledgerSigner.ledgerEVMClient = null as any;
447
+
448
+ // Mock transport for reconnection
449
+ const mockTransport = { close: jest.fn() } as any;
450
+
451
+ // Mock ensureConnection to simulate successful reconnection
452
+ evmKeyring.ledgerSigner.ensureConnection = jest
453
+ .fn()
454
+ .mockImplementation(async () => {
455
+ // Simulate successful reconnection
456
+ evmKeyring.ledgerSigner.transport = mockTransport;
457
+ evmKeyring.ledgerSigner.ledgerUtxoClient = {
458
+ getMasterFingerprint: jest.fn().mockResolvedValue('12345678'),
459
+ } as any;
460
+ evmKeyring.ledgerSigner.ledgerEVMClient = {
461
+ signTransaction: jest.fn().mockResolvedValue({
462
+ r: '123456789abcdef',
463
+ s: '987654321fedcba',
464
+ v: '00',
465
+ }),
466
+ getAddress: jest.fn().mockResolvedValue({
467
+ address: '0x742D35Cc6634C0532925a3b844bc9e7595f2bd9f',
468
+ publicKey: '0x04...',
469
+ }),
470
+ signPersonalMessage: jest
471
+ .fn()
472
+ .mockResolvedValue('0xmocked_signature'),
473
+ signEIP712HashedMessage: jest
474
+ .fn()
475
+ .mockResolvedValue('0xmocked_typed_signature'),
476
+ } as any;
477
+ });
478
+
479
+ // Import Ledger account
480
+ const evmAccount = {
481
+ id: 0,
482
+ label: 'Ledger 1',
483
+ address: '0x742D35Cc6634C0532925a3b844bc9e7595f2bd9f',
484
+ xpub: 'xpub...',
485
+ };
486
+
487
+ evmVaultState.accounts[KeyringAccountType.Ledger][0] = {
488
+ id: evmAccount.id,
489
+ label: evmAccount.label,
490
+ address: evmAccount.address,
491
+ xpub: evmAccount.xpub,
492
+ xprv: '',
493
+ isImported: false,
494
+ isTrezorWallet: false,
495
+ isLedgerWallet: true,
496
+ balances: { syscoin: 0, ethereum: 0 },
497
+ assets: { syscoin: [], ethereum: [] },
498
+ };
499
+
500
+ // Test that Ledger reconnects and signs successfully
501
+ const result = await evmKeyring.ledgerSigner.evm.signEVMTransaction({
502
+ rawTx: '0x1234567890',
503
+ accountIndex: 0,
504
+ });
505
+
506
+ // Verify reconnection happened
507
+ expect(evmKeyring.ledgerSigner.ensureConnection).toHaveBeenCalledTimes(1);
508
+
509
+ // Verify the signature was returned
510
+ expect(result).toEqual({
511
+ r: '123456789abcdef',
512
+ s: '987654321fedcba',
513
+ v: '00',
514
+ });
515
+
516
+ // Verify Ledger is now connected
517
+ expect(evmKeyring.ledgerSigner.transport).toBeTruthy();
518
+ });
519
+
520
+ it('should handle EVM transaction signing for Ledger', async () => {
521
+ // Set up EVM vault state
522
+ const evmVaultState = createMockVaultState({
523
+ activeAccountId: 0,
524
+ activeAccountType: KeyringAccountType.HDAccount,
525
+ networkType: INetworkType.Ethereum,
526
+ chainId: 1,
527
+ });
528
+ const evmVaultStateGetter = jest.fn(() => evmVaultState);
529
+
530
+ // Create EVM keyring with Ledger
531
+ const evmKeyring = await KeyringManager.createInitialized(
532
+ PEACE_SEED_PHRASE,
533
+ FAKE_PASSWORD,
534
+ evmVaultStateGetter
535
+ );
536
+
537
+ // Mock ensureConnection
538
+ evmKeyring.ledgerSigner.ensureConnection = jest
539
+ .fn()
540
+ .mockResolvedValue(undefined);
541
+
542
+ // Mock Ledger EVM signer
543
+ const mockSignEVMTransaction = jest.fn().mockResolvedValue({
544
+ r: '123456789abcdef123456789abcdef123456789abcdef123456789abcdef12345678',
545
+ s: '987654321fedcba987654321fedcba987654321fedcba987654321fedcba987654',
546
+ v: '00',
547
+ });
548
+
549
+ evmKeyring.ledgerSigner.evm = {
550
+ getEvmAddressAndPubKey: jest.fn().mockResolvedValue({
551
+ address: '0x742D35Cc6634C0532925a3b844bc9e7595f2bd9f',
552
+ publicKey: '0x04...',
553
+ }),
554
+ signEVMTransaction: mockSignEVMTransaction,
555
+ signPersonalMessage: jest.fn().mockResolvedValue('0xmocked_signature'),
556
+ signTypedData: jest.fn().mockResolvedValue('0xmocked_typed_signature'),
557
+ };
558
+
559
+ // Import Ledger account
560
+ const evmAccount = await evmKeyring.importLedgerAccount('EVM Ledger');
561
+ if (evmAccount) {
562
+ // Update vault state to set Ledger as active
563
+ evmVaultState.accounts[KeyringAccountType.Ledger][evmAccount.id] = {
564
+ id: evmAccount.id,
565
+ label: evmAccount.label,
566
+ address: evmAccount.address,
567
+ xpub: evmAccount.xpub,
568
+ xprv: '',
569
+ isImported: false,
570
+ isTrezorWallet: false,
571
+ isLedgerWallet: true,
572
+ balances: { syscoin: 0, ethereum: 0 },
573
+ assets: { syscoin: [], ethereum: [] },
574
+ };
575
+ evmVaultState.activeAccount = {
576
+ id: evmAccount.id,
577
+ type: KeyringAccountType.Ledger,
578
+ };
579
+
580
+ // Account switch is handled by vault state update
581
+ }
582
+
583
+ // Test that Ledger signing is called - expect it to fail at serialization but verify Ledger was called
584
+ try {
585
+ await evmKeyring.ethereumTransaction.sendFormattedTransaction({
586
+ to: '0x742D35Cc6634C0532925a3b844bc9e7595f2bd9f',
587
+ value: '0x0',
588
+ data: '0x',
589
+ gasLimit: '0x5208',
590
+ maxFeePerGas: '0x3b9aca00',
591
+ maxPriorityFeePerGas: '0x1dcd6500',
592
+ chainId: 1,
593
+ from: '0x742D35Cc6634C0532925a3b844bc9e7595f2bd9f',
594
+ });
595
+ // If it doesn't throw, that's fine too
596
+ } catch (error) {
597
+ // Expected to fail at signature serialization, but we should have called the Ledger signer
598
+ expect(error.message).toContain('value out of range');
599
+ }
600
+
601
+ // The important part: verify that Ledger signing was attempted with correct parameters
602
+ expect(mockSignEVMTransaction).toHaveBeenCalled();
603
+ expect(mockSignEVMTransaction).toHaveBeenCalledWith({
604
+ rawTx: expect.any(String), // Should be a hex string of the unsigned transaction
605
+ accountIndex: expect.any(Number), // Should be the account index
606
+ });
607
+ });
608
+ });
609
+
610
+ describe('Network Support', () => {
611
+ it('should support Ledger on different UTXO networks', async () => {
612
+ // Set up testnet vault state
613
+ const testnetVaultState = createMockVaultState({
614
+ activeAccountId: 0,
615
+ activeAccountType: KeyringAccountType.HDAccount,
616
+ networkType: INetworkType.Syscoin,
617
+ chainId: 5700,
618
+ });
619
+ const testnetVaultStateGetter = jest.fn(() => testnetVaultState);
620
+
621
+ // Test on testnet
622
+ const testnetKeyring = await KeyringManager.createInitialized(
623
+ PEACE_SEED_PHRASE,
624
+ FAKE_PASSWORD,
625
+ testnetVaultStateGetter
626
+ );
627
+
628
+ // Mock getAddress to return testnet address
629
+ testnetKeyring.getAddress = jest
630
+ .fn()
631
+ .mockResolvedValue('tsys1q_testnet_ledger');
632
+
633
+ // Mock ensureConnection for testnet
634
+ testnetKeyring.ledgerSigner.ensureConnection = jest
635
+ .fn()
636
+ .mockResolvedValue(undefined);
637
+
638
+ // Mock testnet Ledger
639
+ testnetKeyring.ledgerSigner.utxo = {
640
+ getXpub: jest
641
+ .fn()
642
+ .mockResolvedValue(
643
+ 'vpub5YMNvjHGu8MhNvgxNrGV8qZGkb3SVTiCAzqyCV8TbCZrEXrJqsCTMJjEJXBLfmjfFCDPRpGPW59THQMvPDuQejY5cSpfNYVZYcgJaMVZJCG'
644
+ ),
645
+ getUtxoAddress: jest.fn().mockResolvedValue('tsys1q_testnet_ledger'),
646
+ verifyUtxoAddress: jest.fn().mockResolvedValue('tsys1q_testnet_ledger'),
647
+ };
648
+
649
+ const account = await testnetKeyring.importLedgerAccount(
650
+ 'Testnet Ledger'
651
+ );
652
+ if (account) {
653
+ expect(account.address.match(/^(sys1|tsys1)/)).toBeTruthy();
654
+ }
655
+ });
656
+
657
+ it('should maintain separate Ledger accounts per network', async () => {
658
+ // Mock getAddress for mainnet
659
+ keyringManager.getAddress = jest
660
+ .fn()
661
+ .mockResolvedValue('sys1q_mainnet_ledger');
662
+
663
+ // Mock mainnet Ledger
664
+ keyringManager.ledgerSigner.utxo = {
665
+ getXpub: jest
666
+ .fn()
667
+ .mockResolvedValue(
668
+ 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs'
669
+ ),
670
+ getUtxoAddress: jest.fn().mockResolvedValue('sys1q_mainnet_ledger'),
671
+ verifyUtxoAddress: jest.fn().mockResolvedValue('sys1q_mainnet_ledger'),
672
+ };
673
+
674
+ // Import on mainnet
675
+ const mainnetAccount = await keyringManager.importLedgerAccount(
676
+ 'Mainnet Ledger'
677
+ );
678
+
679
+ // Set up testnet vault state
680
+ const testnetVaultState = createMockVaultState({
681
+ activeAccountId: 0,
682
+ activeAccountType: KeyringAccountType.HDAccount,
683
+ networkType: INetworkType.Syscoin,
684
+ chainId: 5700,
685
+ });
686
+ const testnetVaultStateGetter = jest.fn(() => testnetVaultState);
687
+
688
+ // Create testnet keyring
689
+ const testnetKeyring = await KeyringManager.createInitialized(
690
+ PEACE_SEED_PHRASE,
691
+ FAKE_PASSWORD,
692
+ testnetVaultStateGetter
693
+ );
694
+
695
+ // Mock getAddress for testnet
696
+ testnetKeyring.getAddress = jest
697
+ .fn()
698
+ .mockResolvedValue('tsys1q_testnet_ledger');
699
+
700
+ // Mock ensureConnection for testnet
701
+ testnetKeyring.ledgerSigner.ensureConnection = jest
702
+ .fn()
703
+ .mockResolvedValue(undefined);
704
+
705
+ // Mock testnet Ledger
706
+ testnetKeyring.ledgerSigner.utxo = {
707
+ getXpub: jest
708
+ .fn()
709
+ .mockResolvedValue(
710
+ 'vpub5YMNvjHGu8MhNvgxNrGV8qZGkb3SVTiCAzqyCV8TbCZrEXrJqsCTMJjEJXBLfmjfFCDPRpGPW59THQMvPDuQejY5cSpfNYVZYcgJaMVZJCG'
711
+ ),
712
+ getUtxoAddress: jest.fn().mockResolvedValue('tsys1q_testnet_ledger'),
713
+ verifyUtxoAddress: jest.fn().mockResolvedValue('tsys1q_testnet_ledger'),
714
+ };
715
+
716
+ // Import on testnet
717
+ const testnetAccount = await testnetKeyring.importLedgerAccount(
718
+ 'Testnet Ledger'
719
+ );
720
+
721
+ // Accounts should be independent
722
+ if (mainnetAccount && testnetAccount) {
723
+ expect(mainnetAccount.address).not.toBe(testnetAccount.address);
724
+ expect(mainnetAccount.address.match(/^(sys1|tsys1)/)).toBeTruthy();
725
+ expect(testnetAccount.address.match(/^(sys1|tsys1)/)).toBeTruthy();
726
+ }
727
+ });
728
+ });
729
+
730
+ describe('Error Handling', () => {
731
+ it('should handle Ledger communication errors', async () => {
732
+ // Mock communication error
733
+ keyringManager.ledgerSigner.utxo = {
734
+ getXpub: jest
735
+ .fn()
736
+ .mockRejectedValue(new Error('Ledger device communication error')),
737
+ getUtxoAddress: jest.fn(),
738
+ verifyUtxoAddress: jest.fn(),
739
+ };
740
+
741
+ await expect(keyringManager.importLedgerAccount()).rejects.toThrow(
742
+ 'Ledger device communication error'
743
+ );
744
+ });
745
+
746
+ it('should handle invalid Ledger responses', async () => {
747
+ // Mock invalid response
748
+ keyringManager.ledgerSigner.utxo = {
749
+ getXpub: jest.fn().mockResolvedValue(null),
750
+ getUtxoAddress: jest.fn().mockResolvedValue(null),
751
+ verifyUtxoAddress: jest.fn(),
752
+ };
753
+
754
+ await expect(keyringManager.importLedgerAccount()).rejects.toThrow(
755
+ 'Something wrong happened'
756
+ );
757
+ });
758
+
759
+ it('should handle Ledger app not open', async () => {
760
+ keyringManager.ledgerSigner.utxo = {
761
+ getXpub: jest
762
+ .fn()
763
+ .mockRejectedValue(new Error('Please open Syscoin app on Ledger')),
764
+ getUtxoAddress: jest.fn(),
765
+ verifyUtxoAddress: jest.fn(),
766
+ };
767
+
768
+ await expect(keyringManager.importLedgerAccount()).rejects.toThrow(
769
+ 'Please open Syscoin app on Ledger'
770
+ );
771
+ });
772
+ });
773
+
774
+ describe('Security', () => {
775
+ it('should not expose private keys for hardware wallets', async () => {
776
+ // Mock getAddress
777
+ keyringManager.getAddress = jest
778
+ .fn()
779
+ .mockResolvedValue('sys1q_test_address');
780
+
781
+ keyringManager.ledgerSigner.utxo = {
782
+ getXpub: jest
783
+ .fn()
784
+ .mockResolvedValue(
785
+ 'zpub6s8HtEQtcu3AmBn9sniSqCAVhx2nJAhb2sd5NDYeYZ1ZJaZx7MAVZZnG1PdCUNJcVJXGbVpGfSYZLgkPSUjLYnJg8UdYvdkfaygcXZKPLy6'
786
+ ),
787
+ getUtxoAddress: jest.fn().mockResolvedValue('sys1q_test_address'),
788
+ verifyUtxoAddress: jest.fn().mockResolvedValue('sys1q_test_address'),
789
+ };
790
+
791
+ const account = await keyringManager.importLedgerAccount('Security Test');
792
+
793
+ expect(account).toBeDefined();
794
+ if (account) {
795
+ expect(account.xprv).toBe(''); // Should be empty for hardware wallets
796
+ expect(account.isLedgerWallet).toBe(true);
797
+
798
+ // Should not be able to get private key for hardware wallet
799
+ await expect(
800
+ keyringManager.getPrivateKeyByAccountId(
801
+ account.id,
802
+ KeyringAccountType.Ledger,
803
+ FAKE_PASSWORD
804
+ )
805
+ ).rejects.toThrow();
806
+ }
807
+ });
808
+
809
+ it('should maintain hardware wallet isolation', async () => {
810
+ // Mock getAddress
811
+ keyringManager.getAddress = jest
812
+ .fn()
813
+ .mockResolvedValue('sys1q_isolation_address');
814
+
815
+ // Mock and import Ledger account
816
+ keyringManager.ledgerSigner.utxo = {
817
+ getXpub: jest
818
+ .fn()
819
+ .mockResolvedValue(
820
+ 'zpub6s8HtEQtcu3AmBn9sniSqCAVhx2nJAhb2sd5NDYeYZ1ZJaZx7MAVZZnG1PdCUNJcVJXGbVpGfSYZLgkPSUjLYnJg8UdYvdkfaygcXZKPLy6'
821
+ ),
822
+ getUtxoAddress: jest.fn().mockResolvedValue('sys1q_isolation_address'),
823
+ verifyUtxoAddress: jest
824
+ .fn()
825
+ .mockResolvedValue('sys1q_isolation_address'),
826
+ };
827
+
828
+ const ledgerAccount = await keyringManager.importLedgerAccount(
829
+ 'Isolation Test'
830
+ );
831
+
832
+ if (ledgerAccount) {
833
+ // Update vault state
834
+ currentVaultState.accounts[KeyringAccountType.Ledger][
835
+ ledgerAccount.id
836
+ ] = {
837
+ id: ledgerAccount.id,
838
+ label: ledgerAccount.label,
839
+ address: ledgerAccount.address,
840
+ xpub: ledgerAccount.xpub,
841
+ xprv: '',
842
+ isImported: false,
843
+ isTrezorWallet: false,
844
+ isLedgerWallet: true,
845
+ balances: { syscoin: 0, ethereum: 0 },
846
+ assets: { syscoin: [], ethereum: [] },
847
+ };
848
+
849
+ // Verify Ledger accounts are completely separate from HD accounts
850
+ const hdAccounts =
851
+ currentVaultState.accounts[KeyringAccountType.HDAccount];
852
+ const ledgerAccounts =
853
+ currentVaultState.accounts[KeyringAccountType.Ledger];
854
+
855
+ expect(Object.keys(hdAccounts)).toHaveLength(1); // Initial HD account
856
+ expect(Object.keys(ledgerAccounts)).toHaveLength(1); // One imported Ledger account
857
+
858
+ // Verify no cross-contamination of account types
859
+ Object.values(hdAccounts).forEach((account: any) => {
860
+ expect(account.isLedgerWallet).toBe(false);
861
+ });
862
+
863
+ Object.values(ledgerAccounts).forEach((account: any) => {
864
+ expect(account.isLedgerWallet).toBe(true);
865
+ });
866
+ }
867
+ });
868
+ });
869
+ });