@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,970 @@
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
+ describe('KeyringManager - Account Management', () => {
8
+ let keyringManager: KeyringManager;
9
+ let mockVaultStateGetter: jest.Mock;
10
+ let currentVaultState: any;
11
+
12
+ beforeEach(async () => {
13
+ setupMocks();
14
+
15
+ // Set up mock vault state for testing
16
+ currentVaultState = createMockVaultState({
17
+ activeAccountId: 0,
18
+ activeAccountType: KeyringAccountType.HDAccount,
19
+ networkType: INetworkType.Ethereum,
20
+ chainId: 1,
21
+ });
22
+
23
+ // Set up vault-keys that would normally be created by Pali's MainController
24
+ await setupTestVault(FAKE_PASSWORD);
25
+
26
+ // Create mock vault getter that returns the current state
27
+ mockVaultStateGetter = jest.fn(() => currentVaultState);
28
+
29
+ // Create keyring manager with vault getter
30
+ keyringManager = await KeyringManager.createInitialized(
31
+ PEACE_SEED_PHRASE,
32
+ FAKE_PASSWORD,
33
+ mockVaultStateGetter
34
+ );
35
+ });
36
+
37
+ describe('HD Account Management', () => {
38
+ it('should add new HD accounts with sequential IDs', async () => {
39
+ // Initial account should be 0
40
+ const initialAccount = keyringManager.getActiveAccount().activeAccount;
41
+ expect(initialAccount.id).toBe(0);
42
+ expect(initialAccount.label).toBe('Account 1');
43
+
44
+ // Add second account
45
+ const account2 = await keyringManager.addNewAccount();
46
+ expect(account2.id).toBe(1);
47
+ expect(account2.label).toBe('Account 2');
48
+ expect(account2.isImported).toBe(false);
49
+
50
+ // Update vault state to include the new account
51
+ currentVaultState.accounts[KeyringAccountType.HDAccount][1] = {
52
+ id: 1,
53
+ label: 'Account 2',
54
+ address: account2.address,
55
+ xpub: account2.xpub,
56
+ xprv: account2.xprv,
57
+ isImported: false,
58
+ isTrezorWallet: false,
59
+ isLedgerWallet: false,
60
+ balances: { syscoin: 0, ethereum: 0 },
61
+ assets: { syscoin: [], ethereum: [] },
62
+ };
63
+
64
+ // Add third account with custom label
65
+ const account3 = await keyringManager.addNewAccount('My Custom Account');
66
+ expect(account3.id).toBe(2);
67
+ expect(account3.label).toBe('My Custom Account');
68
+
69
+ // Update vault state for account 3
70
+ currentVaultState.accounts[KeyringAccountType.HDAccount][2] = {
71
+ id: 2,
72
+ label: 'My Custom Account',
73
+ address: account3.address,
74
+ xpub: account3.xpub,
75
+ xprv: account3.xprv,
76
+ isImported: false,
77
+ isTrezorWallet: false,
78
+ isLedgerWallet: false,
79
+ balances: { syscoin: 0, ethereum: 0 },
80
+ assets: { syscoin: [], ethereum: [] },
81
+ };
82
+ });
83
+
84
+ it('should switch between HD accounts', async () => {
85
+ // First create the accounts and update vault state
86
+ const account2 = await keyringManager.addNewAccount();
87
+ const account3 = await keyringManager.addNewAccount();
88
+
89
+ // Update vault state to include all accounts
90
+ currentVaultState.accounts[KeyringAccountType.HDAccount][1] = {
91
+ id: 1,
92
+ label: 'Account 2',
93
+ address: account2.address,
94
+ xpub: account2.xpub,
95
+ xprv: '',
96
+ isImported: false,
97
+ isTrezorWallet: false,
98
+ isLedgerWallet: false,
99
+ balances: { syscoin: 0, ethereum: 0 },
100
+ assets: { syscoin: [], ethereum: [] },
101
+ };
102
+ currentVaultState.accounts[KeyringAccountType.HDAccount][2] = {
103
+ id: 2,
104
+ label: 'Account 3',
105
+ address: account3.address,
106
+ xpub: account3.xpub,
107
+ xprv: '',
108
+ isImported: false,
109
+ isTrezorWallet: false,
110
+ isLedgerWallet: false,
111
+ balances: { syscoin: 0, ethereum: 0 },
112
+ assets: { syscoin: [], ethereum: [] },
113
+ };
114
+
115
+ // Switch to account 1 (only update vault state - no setActiveAccount needed)
116
+ currentVaultState.activeAccount = {
117
+ id: 1,
118
+ type: KeyringAccountType.HDAccount,
119
+ };
120
+ let active = keyringManager.getActiveAccount();
121
+ expect(active.activeAccount.id).toBe(1);
122
+ expect(active.activeAccountType).toBe(KeyringAccountType.HDAccount);
123
+
124
+ // Switch to account 2 (only update vault state)
125
+ currentVaultState.activeAccount = {
126
+ id: 2,
127
+ type: KeyringAccountType.HDAccount,
128
+ };
129
+ active = keyringManager.getActiveAccount();
130
+ expect(active.activeAccount.id).toBe(2);
131
+
132
+ // Switch back to account 0 (only update vault state)
133
+ currentVaultState.activeAccount = {
134
+ id: 0,
135
+ type: KeyringAccountType.HDAccount,
136
+ };
137
+ active = keyringManager.getActiveAccount();
138
+ expect(active.activeAccount.id).toBe(0);
139
+ });
140
+
141
+ it('should get account by ID', async () => {
142
+ // Add account and update vault state
143
+ const newAccount = await keyringManager.addNewAccount('Test Account');
144
+ currentVaultState.accounts[KeyringAccountType.HDAccount][1] = {
145
+ id: 1,
146
+ label: 'Test Account',
147
+ address: newAccount.address,
148
+ xpub: newAccount.xpub,
149
+ xprv: '',
150
+ isImported: false,
151
+ isTrezorWallet: false,
152
+ isLedgerWallet: false,
153
+ balances: { syscoin: 0, ethereum: 0 },
154
+ assets: { syscoin: [], ethereum: [] },
155
+ };
156
+
157
+ const account0 = keyringManager.getAccountById(
158
+ 0,
159
+ KeyringAccountType.HDAccount
160
+ );
161
+ expect(account0.id).toBe(0);
162
+ expect(account0.label).toBe('Account 1');
163
+
164
+ const account1 = keyringManager.getAccountById(
165
+ 1,
166
+ KeyringAccountType.HDAccount
167
+ );
168
+ expect(account1.id).toBe(1);
169
+ expect(account1.label).toBe('Test Account');
170
+ });
171
+
172
+ it('should throw when getting non-existent account', () => {
173
+ expect(() =>
174
+ keyringManager.getAccountById(999, KeyringAccountType.HDAccount)
175
+ ).toThrow('Account not found');
176
+ });
177
+
178
+ it('should generate correct network-specific addresses when switching between different slip44 networks', async () => {
179
+ // This test verifies the fix for the bug where cached accounts
180
+ // retained stale addresses from previous networks
181
+
182
+ // Step 1: Create keyring for Syscoin mainnet (slip44=57)
183
+ const mainnetVaultState = createMockVaultState({
184
+ activeAccountId: 0,
185
+ activeAccountType: KeyringAccountType.HDAccount,
186
+ networkType: INetworkType.Syscoin,
187
+ chainId: 57, // Syscoin mainnet
188
+ });
189
+ const mainnetVaultStateGetter = jest.fn(() => mainnetVaultState);
190
+
191
+ const mainnetKeyring = await KeyringManager.createInitialized(
192
+ PEACE_SEED_PHRASE,
193
+ FAKE_PASSWORD,
194
+ mainnetVaultStateGetter
195
+ );
196
+
197
+ // Get mainnet account with sys1... address
198
+ const mainnetAccount = mainnetKeyring.getActiveAccount().activeAccount;
199
+ expect(mainnetAccount.address.startsWith('sys1')).toBe(true);
200
+
201
+ // Step 2: Simulate caching this account in vault state
202
+ // This represents what happens when we cache vault state
203
+ mainnetVaultState.accounts[KeyringAccountType.HDAccount][0] = {
204
+ ...mainnetAccount,
205
+ address: mainnetAccount.address, // This will be sys1... address
206
+ xpub: mainnetAccount.xpub,
207
+ balances: { syscoin: 10, ethereum: 0 }, // Some mainnet balance
208
+ } as any;
209
+
210
+ // Step 3: Create keyring for Syscoin testnet (slip44=1) with SAME vault state
211
+ // This simulates loading cached vault state that has stale mainnet addresses
212
+ const testnetVaultState = createMockVaultState({
213
+ activeAccountId: 0,
214
+ activeAccountType: KeyringAccountType.HDAccount,
215
+ networkType: INetworkType.Syscoin,
216
+ chainId: 5700, // Syscoin testnet
217
+ });
218
+
219
+ // CRITICAL: Inject the stale mainnet account into testnet vault state
220
+ // This represents the bug scenario where cached vault has wrong addresses
221
+ testnetVaultState.accounts[KeyringAccountType.HDAccount][0] = {
222
+ ...mainnetVaultState.accounts[KeyringAccountType.HDAccount][0],
223
+ // This account still has sys1... address but we're now on testnet
224
+ };
225
+
226
+ const testnetVaultStateGetter = jest.fn(() => testnetVaultState);
227
+
228
+ const testnetKeyring = await KeyringManager.createInitialized(
229
+ PEACE_SEED_PHRASE,
230
+ FAKE_PASSWORD,
231
+ testnetVaultStateGetter
232
+ );
233
+
234
+ // Step 4: Create/load account on testnet - this should generate fresh tsys1... address
235
+ // With the bug: would reuse cached sys1... address
236
+ // With the fix: should derive fresh tsys1... address
237
+ const testnetAccount = await testnetKeyring.createFirstAccount(
238
+ 'Test Account'
239
+ );
240
+
241
+ // Step 5: Verify addresses are network-specific
242
+ expect(mainnetAccount.address.startsWith('sys1')).toBe(true);
243
+ expect(testnetAccount.address.startsWith('tsys1')).toBe(true);
244
+
245
+ // Step 6: Addresses should be different (different networks)
246
+ expect(mainnetAccount.address).not.toBe(testnetAccount.address);
247
+
248
+ // Step 7: xpub formats should also be different
249
+ expect(mainnetAccount.xpub.startsWith('zpub')).toBe(true); // Mainnet format
250
+ expect(testnetAccount.xpub.startsWith('vpub')).toBe(true); // Testnet format
251
+
252
+ // Step 8: Verify testnet account has fresh balance, not cached mainnet balance
253
+ expect(testnetAccount.balances.syscoin).toBe(0); // Fresh account
254
+ expect(testnetAccount.balances.ethereum).toBe(0);
255
+ });
256
+
257
+ it('should handle address generation consistently across multiple account operations', async () => {
258
+ // Test that all account operations use fresh derived addresses
259
+
260
+ const testnetVaultState = createMockVaultState({
261
+ activeAccountId: 0,
262
+ activeAccountType: KeyringAccountType.HDAccount,
263
+ networkType: INetworkType.Syscoin,
264
+ chainId: 5700,
265
+ });
266
+
267
+ // Inject stale mainnet address to simulate the bug condition
268
+ testnetVaultState.accounts[KeyringAccountType.HDAccount][0] = {
269
+ id: 0,
270
+ label: 'Account 1',
271
+ address: 'sys1qbuggyaddressfromcachedmainnetstate', // Wrong network address
272
+ xpub: 'zpub6rSLPXDUvvuy5VEPMUTkiTYfpr7vYACJKsySDMgng1rwEU', // Wrong format
273
+ xprv: 'encrypted_xprv',
274
+ balances: { syscoin: 5, ethereum: 0 }, // Stale balance
275
+ isImported: false,
276
+ isTrezorWallet: false,
277
+ isLedgerWallet: false,
278
+ } as any;
279
+
280
+ const vaultStateGetter = jest.fn(() => testnetVaultState);
281
+
282
+ const keyringManager = await KeyringManager.createInitialized(
283
+ PEACE_SEED_PHRASE,
284
+ FAKE_PASSWORD,
285
+ vaultStateGetter
286
+ );
287
+
288
+ // All these operations should use correctly derived testnet addresses
289
+ const firstAccount = await keyringManager.createFirstAccount();
290
+
291
+ // Update the vault state to reflect the correctly generated first account
292
+ // This simulates what would happen in real usage where the vault state gets updated
293
+ testnetVaultState.accounts[KeyringAccountType.HDAccount][0] = {
294
+ id: 0,
295
+ label: 'Account 1',
296
+ address: firstAccount.address, // Now has correct tsys1... address
297
+ xpub: firstAccount.xpub,
298
+ xprv: 'encrypted_xprv',
299
+ balances: { syscoin: 0, ethereum: 0 },
300
+ isImported: false,
301
+ isTrezorWallet: false,
302
+ isLedgerWallet: false,
303
+ } as any;
304
+
305
+ const newAccount = await keyringManager.addNewAccount('Account 2');
306
+ const activeAccount = keyringManager.getActiveAccount().activeAccount;
307
+
308
+ // All should have correct testnet addresses
309
+ expect(firstAccount.address.startsWith('tsys1')).toBe(true);
310
+ expect(newAccount.address.startsWith('tsys1')).toBe(true);
311
+ expect(activeAccount.address.startsWith('tsys1')).toBe(true);
312
+
313
+ // Should not have the buggy cached address
314
+ expect(firstAccount.address).not.toBe(
315
+ 'sys1qbuggyaddressfromcachedmainnetstate'
316
+ );
317
+ expect(activeAccount.address).not.toBe(
318
+ 'sys1qbuggyaddressfromcachedmainnetstate'
319
+ );
320
+ });
321
+ });
322
+
323
+ describe('Imported Account Management', () => {
324
+ describe('EVM Import', () => {
325
+ it('should import account from private key', async () => {
326
+ const privateKey =
327
+ '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318';
328
+ const expectedAddress = '0x2c7536E3605D9C16a7a3D7b1898e529396a65c23';
329
+
330
+ const imported = await keyringManager.importAccount(privateKey);
331
+
332
+ // Update vault state to reflect import
333
+ currentVaultState.accounts[KeyringAccountType.Imported][0] = {
334
+ id: 0,
335
+ label: 'Imported 1',
336
+ address: imported.address,
337
+ xpub: imported.xpub,
338
+ xprv: imported.xprv,
339
+ isImported: true,
340
+ isTrezorWallet: false,
341
+ isLedgerWallet: false,
342
+ balances: { syscoin: 0, ethereum: 0 },
343
+ assets: { syscoin: [], ethereum: [] },
344
+ };
345
+
346
+ expect(imported.isImported).toBe(true);
347
+ expect(imported.address.toLowerCase()).toBe(
348
+ expectedAddress.toLowerCase()
349
+ );
350
+ expect(imported.id).toBe(0);
351
+ expect(imported.label).toBe('Imported 1');
352
+ });
353
+
354
+ it('should import account with custom label', async () => {
355
+ const privateKey =
356
+ '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318';
357
+ const imported = await keyringManager.importAccount(
358
+ privateKey,
359
+ 'My Hardware Wallet'
360
+ );
361
+ expect(imported.label).toBe('My Hardware Wallet');
362
+ });
363
+
364
+ it('should handle multiple imported accounts', async () => {
365
+ const privateKey1 =
366
+ '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318';
367
+ const privateKey2 =
368
+ '0x1234567890123456789012345678901234567890123456789012345678901234';
369
+
370
+ const imported1 = await keyringManager.importAccount(privateKey1);
371
+
372
+ // Update vault state for first import
373
+ currentVaultState.accounts[KeyringAccountType.Imported][0] = {
374
+ id: 0,
375
+ label: 'Imported 1',
376
+ address: imported1.address,
377
+ xpub: imported1.xpub,
378
+ xprv: imported1.xprv,
379
+ isImported: true,
380
+ isTrezorWallet: false,
381
+ isLedgerWallet: false,
382
+ balances: { syscoin: 0, ethereum: 0 },
383
+ assets: { syscoin: [], ethereum: [] },
384
+ };
385
+
386
+ const imported2 = await keyringManager.importAccount(privateKey2);
387
+
388
+ // Update vault state for second import
389
+ currentVaultState.accounts[KeyringAccountType.Imported][1] = {
390
+ id: 1,
391
+ label: 'Imported 2',
392
+ address: imported2.address,
393
+ xpub: imported2.xpub,
394
+ xprv: imported2.xprv,
395
+ isImported: true,
396
+ isTrezorWallet: false,
397
+ isLedgerWallet: false,
398
+ balances: { syscoin: 0, ethereum: 0 },
399
+ assets: { syscoin: [], ethereum: [] },
400
+ };
401
+
402
+ expect(imported1.id).toBe(0);
403
+ expect(imported2.id).toBe(1);
404
+ expect(imported2.label).toBe('Imported 2');
405
+ });
406
+
407
+ it('should get private key for imported account', async () => {
408
+ const privateKey =
409
+ '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318';
410
+ const imported = await keyringManager.importAccount(privateKey);
411
+
412
+ // Update vault state
413
+ currentVaultState.accounts[KeyringAccountType.Imported][imported.id] = {
414
+ id: imported.id,
415
+ label: imported.label,
416
+ address: imported.address,
417
+ xpub: imported.xpub,
418
+ xprv: imported.xprv,
419
+ isImported: true,
420
+ isTrezorWallet: false,
421
+ isLedgerWallet: false,
422
+ balances: { syscoin: 0, ethereum: 0 },
423
+ assets: { syscoin: [], ethereum: [] },
424
+ };
425
+
426
+ const retrievedKey = await keyringManager.getPrivateKeyByAccountId(
427
+ imported.id,
428
+ KeyringAccountType.Imported,
429
+ FAKE_PASSWORD
430
+ );
431
+
432
+ expect(retrievedKey).toBe(privateKey);
433
+ });
434
+
435
+ it('should reject private key retrieval with wrong password', async () => {
436
+ const privateKey =
437
+ '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318';
438
+ const imported = await keyringManager.importAccount(privateKey);
439
+
440
+ // Update vault state
441
+ currentVaultState.accounts[KeyringAccountType.Imported][imported.id] = {
442
+ id: imported.id,
443
+ label: imported.label,
444
+ address: imported.address,
445
+ xpub: imported.xpub,
446
+ xprv: imported.xprv,
447
+ isImported: true,
448
+ isTrezorWallet: false,
449
+ isLedgerWallet: false,
450
+ balances: { syscoin: 0, ethereum: 0 },
451
+ assets: { syscoin: [], ethereum: [] },
452
+ };
453
+
454
+ await expect(
455
+ keyringManager.getPrivateKeyByAccountId(
456
+ imported.id,
457
+ KeyringAccountType.Imported,
458
+ 'wrong_password'
459
+ )
460
+ ).rejects.toThrow('Invalid password');
461
+ });
462
+ });
463
+
464
+ describe('UTXO Import', () => {
465
+ beforeEach(() => {
466
+ // Set up UTXO network state
467
+ currentVaultState = createMockVaultState({
468
+ activeAccountId: 0,
469
+ activeAccountType: KeyringAccountType.HDAccount,
470
+ networkType: INetworkType.Syscoin,
471
+ chainId: 57,
472
+ });
473
+ mockVaultStateGetter.mockReturnValue(currentVaultState);
474
+ });
475
+
476
+ it('should import UTXO account from mainnet zprv', async () => {
477
+ const mainnetZprv =
478
+ 'zprvAdGDwa3WySqQoVwVSbYRMKxDhSXpK2wW6wDjekCMdm7TaQ3igf52xRRjYghTvnFurtMm6CMgQivEDJs5ixGSnTtv8usFmkAoTe6XCF5hnpR';
479
+ const imported = await keyringManager.importAccount(mainnetZprv);
480
+
481
+ expect(imported.isImported).toBe(true);
482
+ expect(imported.address.startsWith('sys1')).toBe(true);
483
+ expect(imported.id).toBe(0);
484
+ expect(imported.label).toBe('Imported 1');
485
+ });
486
+
487
+ it('should validate zprv before import', async () => {
488
+ const invalidZprv = 'invalid_zprv_string';
489
+ await expect(
490
+ keyringManager.importAccount(invalidZprv)
491
+ ).rejects.toThrow();
492
+ });
493
+
494
+ it('should switch between HD and imported UTXO accounts', async () => {
495
+ const mainnetZprv =
496
+ 'zprvAdGDwa3WySqQoVwVSbYRMKxDhSXpK2wW6wDjekCMdm7TaQ3igf52xRRjYghTvnFurtMm6CMgQivEDJs5ixGSnTtv8usFmkAoTe6XCF5hnpR';
497
+ const imported = await keyringManager.importAccount(mainnetZprv);
498
+
499
+ // Update vault state with imported account
500
+ currentVaultState.accounts[KeyringAccountType.Imported][imported.id] = {
501
+ id: imported.id,
502
+ label: imported.label,
503
+ address: imported.address,
504
+ xpub: imported.xpub,
505
+ xprv: imported.xprv,
506
+ isImported: true,
507
+ isTrezorWallet: false,
508
+ isLedgerWallet: false,
509
+ balances: { syscoin: 0, ethereum: 0 },
510
+ assets: { syscoin: [], ethereum: [] },
511
+ };
512
+
513
+ // Switch to imported account (only update vault state)
514
+ currentVaultState.activeAccount = {
515
+ id: imported.id,
516
+ type: KeyringAccountType.Imported,
517
+ };
518
+ let active = keyringManager.getActiveAccount();
519
+ expect(active.activeAccount.id).toBe(imported.id);
520
+ expect(active.activeAccountType).toBe(KeyringAccountType.Imported);
521
+ expect(active.activeAccount.address).toBe(imported.address);
522
+
523
+ // Switch back to HD account (only update vault state)
524
+ currentVaultState.activeAccount = {
525
+ id: 0,
526
+ type: KeyringAccountType.HDAccount,
527
+ };
528
+ active = keyringManager.getActiveAccount();
529
+ expect(active.activeAccount.id).toBe(0);
530
+ expect(active.activeAccountType).toBe(KeyringAccountType.HDAccount);
531
+ });
532
+ });
533
+ });
534
+
535
+ describe('Mixed Account Types', () => {
536
+ it('should handle switching between different account types', async () => {
537
+ // Add HD accounts
538
+ const hdAccount2 = await keyringManager.addNewAccount('HD Account 2');
539
+
540
+ // Import account
541
+ const privateKey =
542
+ '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318';
543
+ const imported = await keyringManager.importAccount(
544
+ privateKey,
545
+ 'Imported Account'
546
+ );
547
+
548
+ // Update vault state with all accounts
549
+ currentVaultState.accounts[KeyringAccountType.HDAccount][1] = {
550
+ id: 1,
551
+ label: 'HD Account 2',
552
+ address: hdAccount2.address,
553
+ xpub: hdAccount2.xpub,
554
+ xprv: '',
555
+ isImported: false,
556
+ isTrezorWallet: false,
557
+ isLedgerWallet: false,
558
+ balances: { syscoin: 0, ethereum: 0 },
559
+ assets: { syscoin: [], ethereum: [] },
560
+ };
561
+ currentVaultState.accounts[KeyringAccountType.Imported][imported.id] = {
562
+ id: imported.id,
563
+ label: 'Imported Account',
564
+ address: imported.address,
565
+ xpub: imported.xpub,
566
+ xprv: imported.xprv,
567
+ isImported: true,
568
+ isTrezorWallet: false,
569
+ isLedgerWallet: false,
570
+ balances: { syscoin: 0, ethereum: 0 },
571
+ assets: { syscoin: [], ethereum: [] },
572
+ };
573
+
574
+ // Test switching between all accounts
575
+ const switches = [
576
+ { id: 0, type: KeyringAccountType.HDAccount, label: 'Account 1' },
577
+ { id: 1, type: KeyringAccountType.HDAccount, label: 'HD Account 2' },
578
+ {
579
+ id: imported.id,
580
+ type: KeyringAccountType.Imported,
581
+ label: 'Imported Account',
582
+ },
583
+ ];
584
+
585
+ for (const { id, type, label } of switches) {
586
+ currentVaultState.activeAccount = { id, type };
587
+ const active = keyringManager.getActiveAccount();
588
+ expect(active.activeAccount.id).toBe(id);
589
+ expect(active.activeAccountType).toBe(type);
590
+ expect(active.activeAccount.label).toBe(label);
591
+ }
592
+ });
593
+
594
+ it('should maintain separate ID sequences for each account type', async () => {
595
+ // Add HD accounts
596
+ const hd1 = await keyringManager.addNewAccount();
597
+
598
+ // Update vault state to include hd1 so hd2 gets the right ID
599
+ currentVaultState.accounts[KeyringAccountType.HDAccount][1] = {
600
+ id: 1,
601
+ label: 'Account 2',
602
+ address: hd1.address,
603
+ xpub: hd1.xpub,
604
+ xprv: '',
605
+ isImported: false,
606
+ isTrezorWallet: false,
607
+ isLedgerWallet: false,
608
+ balances: { syscoin: 0, ethereum: 0 },
609
+ assets: { syscoin: [], ethereum: [] },
610
+ };
611
+
612
+ const hd2 = await keyringManager.addNewAccount();
613
+
614
+ // Import accounts
615
+ const imported1 = await keyringManager.importAccount(
616
+ '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318'
617
+ );
618
+
619
+ // Update vault state to include imported1 so imported2 gets the right ID
620
+ currentVaultState.accounts[KeyringAccountType.Imported][0] = {
621
+ id: 0,
622
+ label: 'Imported 1',
623
+ address: imported1.address,
624
+ xpub: imported1.xpub,
625
+ xprv: imported1.xprv,
626
+ isImported: true,
627
+ isTrezorWallet: false,
628
+ isLedgerWallet: false,
629
+ balances: { syscoin: 0, ethereum: 0 },
630
+ assets: { syscoin: [], ethereum: [] },
631
+ };
632
+
633
+ const imported2 = await keyringManager.importAccount(
634
+ '0x1234567890123456789012345678901234567890123456789012345678901234'
635
+ );
636
+
637
+ // HD accounts should have sequential IDs starting from 0
638
+ expect(hd1.id).toBe(1);
639
+ expect(hd2.id).toBe(2);
640
+
641
+ // Imported accounts should have their own ID sequence starting from 0
642
+ expect(imported1.id).toBe(0);
643
+ expect(imported2.id).toBe(1);
644
+ });
645
+ });
646
+
647
+ describe('Account State Management', () => {
648
+ it('should preserve account state across lock/unlock', async () => {
649
+ // Add accounts
650
+ const hdAccount = await keyringManager.addNewAccount('Test Account');
651
+ const imported = await keyringManager.importAccount(
652
+ '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318',
653
+ 'Imported Test'
654
+ );
655
+
656
+ // Update vault state
657
+ currentVaultState.accounts[KeyringAccountType.HDAccount][1] = {
658
+ id: 1,
659
+ label: 'Test Account',
660
+ address: hdAccount.address,
661
+ xpub: hdAccount.xpub,
662
+ xprv: '',
663
+ isImported: false,
664
+ isTrezorWallet: false,
665
+ isLedgerWallet: false,
666
+ balances: { syscoin: 0, ethereum: 0 },
667
+ assets: { syscoin: [], ethereum: [] },
668
+ };
669
+ currentVaultState.accounts[KeyringAccountType.Imported][imported.id] = {
670
+ id: imported.id,
671
+ label: 'Imported Test',
672
+ address: imported.address,
673
+ xpub: imported.xpub,
674
+ xprv: imported.xprv,
675
+ isImported: true,
676
+ isTrezorWallet: false,
677
+ isLedgerWallet: false,
678
+ balances: { syscoin: 0, ethereum: 0 },
679
+ assets: { syscoin: [], ethereum: [] },
680
+ };
681
+
682
+ // Switch to imported account (only update vault state)
683
+ currentVaultState.activeAccount = {
684
+ id: imported.id,
685
+ type: KeyringAccountType.Imported,
686
+ };
687
+
688
+ // Lock and unlock
689
+ keyringManager.lockWallet();
690
+ await keyringManager.unlock(FAKE_PASSWORD);
691
+
692
+ // Active account should still be the imported one
693
+ const active = keyringManager.getActiveAccount();
694
+ expect(active.activeAccount.id).toBe(imported.id);
695
+ expect(active.activeAccountType).toBe(KeyringAccountType.Imported);
696
+ expect(active.activeAccount.label).toBe('Imported Test');
697
+ });
698
+
699
+ it('should get account xpub', async () => {
700
+ const xpub = keyringManager.getAccountXpub();
701
+ expect(xpub).toBeDefined();
702
+ expect(typeof xpub).toBe('string');
703
+ });
704
+
705
+ it('should not expose private key in account data', async () => {
706
+ const account = keyringManager.getActiveAccount().activeAccount;
707
+ expect(account).not.toHaveProperty('xprv');
708
+ expect(account).toHaveProperty('xpub');
709
+ });
710
+ });
711
+
712
+ describe('Edge Cases', () => {
713
+ it('should handle invalid account type when getting account', async () => {
714
+ expect(() =>
715
+ keyringManager.getAccountById(0, 'INVALID_TYPE' as any)
716
+ ).toThrow();
717
+ });
718
+
719
+ it('should handle getting non-existent account', async () => {
720
+ expect(() =>
721
+ keyringManager.getAccountById(999, KeyringAccountType.HDAccount)
722
+ ).toThrow('Account not found');
723
+ });
724
+
725
+ it('should reject import of invalid private key', async () => {
726
+ await expect(
727
+ keyringManager.importAccount('not_a_valid_key')
728
+ ).rejects.toThrow();
729
+ });
730
+
731
+ it('should handle getNextAccountId with empty accounts', () => {
732
+ // This is a private method, but we can test it indirectly
733
+ // by checking that first account gets ID 0
734
+ const account = keyringManager.getActiveAccount().activeAccount;
735
+ expect(account.id).toBe(0);
736
+ });
737
+
738
+ it('should fill gaps in account IDs when accounts are removed', async () => {
739
+ // Create multiple accounts to simulate a scenario where some are removed
740
+ const account1 = await keyringManager.addNewAccount('Account 1');
741
+ expect(account1.id).toBe(1);
742
+
743
+ // Update vault state to include account1
744
+ currentVaultState.accounts[KeyringAccountType.HDAccount][1] = {
745
+ id: 1,
746
+ label: 'Account 1',
747
+ address: account1.address,
748
+ xpub: account1.xpub,
749
+ xprv: '',
750
+ isImported: false,
751
+ isTrezorWallet: false,
752
+ isLedgerWallet: false,
753
+ balances: { syscoin: 0, ethereum: 0 },
754
+ assets: { syscoin: [], ethereum: [] },
755
+ };
756
+
757
+ const account2 = await keyringManager.addNewAccount('Account 2');
758
+ expect(account2.id).toBe(2);
759
+
760
+ // Update vault state to include account2
761
+ currentVaultState.accounts[KeyringAccountType.HDAccount][2] = {
762
+ id: 2,
763
+ label: 'Account 2',
764
+ address: account2.address,
765
+ xpub: account2.xpub,
766
+ xprv: '',
767
+ isImported: false,
768
+ isTrezorWallet: false,
769
+ isLedgerWallet: false,
770
+ balances: { syscoin: 0, ethereum: 0 },
771
+ assets: { syscoin: [], ethereum: [] },
772
+ };
773
+
774
+ const account3 = await keyringManager.addNewAccount('Account 3');
775
+ expect(account3.id).toBe(3);
776
+
777
+ // Update vault state to include account3
778
+ currentVaultState.accounts[KeyringAccountType.HDAccount][3] = {
779
+ id: 3,
780
+ label: 'Account 3',
781
+ address: account3.address,
782
+ xpub: account3.xpub,
783
+ xprv: '',
784
+ isImported: false,
785
+ isTrezorWallet: false,
786
+ isLedgerWallet: false,
787
+ balances: { syscoin: 0, ethereum: 0 },
788
+ assets: { syscoin: [], ethereum: [] },
789
+ };
790
+
791
+ // Simulate account deletion by removing account with ID 1 from vault state
792
+ // (In real usage, this would be done by the removeAccount controller method)
793
+ delete currentVaultState.accounts[KeyringAccountType.HDAccount][1];
794
+
795
+ // Now when we create a new account, it should fill the gap at ID 1
796
+ const newAccount = await keyringManager.addNewAccount('New Account');
797
+ expect(newAccount.id).toBe(1); // Should reuse the deleted account's ID
798
+
799
+ // Update vault state to include the new account
800
+ currentVaultState.accounts[KeyringAccountType.HDAccount][1] = {
801
+ id: 1,
802
+ label: 'New Account',
803
+ address: newAccount.address,
804
+ xpub: newAccount.xpub,
805
+ xprv: '',
806
+ isImported: false,
807
+ isTrezorWallet: false,
808
+ isLedgerWallet: false,
809
+ balances: { syscoin: 0, ethereum: 0 },
810
+ assets: { syscoin: [], ethereum: [] },
811
+ };
812
+
813
+ // At this point we have accounts: [0, 1, 2, 3] - no gaps
814
+ // Next account should get the next sequential ID
815
+ const anotherAccount = await keyringManager.addNewAccount(
816
+ 'Another Account'
817
+ );
818
+ expect(anotherAccount.id).toBe(4); // Should continue sequence after 3
819
+
820
+ // Update vault state to include anotherAccount
821
+ currentVaultState.accounts[KeyringAccountType.HDAccount][4] = {
822
+ id: 4,
823
+ label: 'Another Account',
824
+ address: anotherAccount.address,
825
+ xpub: anotherAccount.xpub,
826
+ xprv: '',
827
+ isImported: false,
828
+ isTrezorWallet: false,
829
+ isLedgerWallet: false,
830
+ balances: { syscoin: 0, ethereum: 0 },
831
+ assets: { syscoin: [], ethereum: [] },
832
+ };
833
+
834
+ // Simulate removing the middle account (ID 2)
835
+ delete currentVaultState.accounts[KeyringAccountType.HDAccount][2];
836
+
837
+ // Now we have accounts: [0, 1, 3, 4] - gap at ID 2
838
+ // Next account should fill that gap
839
+ const gapFillerAccount = await keyringManager.addNewAccount('Gap Filler');
840
+ expect(gapFillerAccount.id).toBe(2); // Should reuse ID 2
841
+ });
842
+ });
843
+
844
+ describe('Account Label Consistency', () => {
845
+ it('should create EVM accounts with generic labels regardless of network', async () => {
846
+ // Test that EVM accounts always get generic "Account N" labels
847
+ // regardless of which specific EVM network they're created on
848
+
849
+ // Set up NEVM Testnet vault state
850
+ const nevmTestnetVaultState = createMockVaultState({
851
+ activeAccountId: 0,
852
+ activeAccountType: KeyringAccountType.HDAccount,
853
+ networkType: INetworkType.Ethereum,
854
+ chainId: 5700, // Syscoin NEVM Testnet
855
+ });
856
+ const nevmTestnetVaultGetter = jest.fn(() => nevmTestnetVaultState);
857
+
858
+ // Create keyring on NEVM Testnet
859
+ const nevmKeyring = await KeyringManager.createInitialized(
860
+ PEACE_SEED_PHRASE,
861
+ FAKE_PASSWORD,
862
+ nevmTestnetVaultGetter
863
+ );
864
+
865
+ // Create accounts on NEVM Testnet
866
+ const account1 = nevmKeyring.getActiveAccount().activeAccount;
867
+ const account2 = await nevmKeyring.addNewAccount();
868
+
869
+ // Update vault state to include account2 BEFORE creating account3
870
+ nevmTestnetVaultState.accounts[KeyringAccountType.HDAccount][1] = {
871
+ id: 1,
872
+ label: account2.label,
873
+ address: account2.address,
874
+ xpub: account2.xpub,
875
+ xprv: '',
876
+ isImported: false,
877
+ isTrezorWallet: false,
878
+ isLedgerWallet: false,
879
+ balances: { syscoin: 0, ethereum: 0 },
880
+ assets: { syscoin: [], ethereum: [] },
881
+ };
882
+
883
+ const account3 = await nevmKeyring.addNewAccount();
884
+
885
+ // Update vault state to include account3
886
+ nevmTestnetVaultState.accounts[KeyringAccountType.HDAccount][2] = {
887
+ id: 2,
888
+ label: account3.label,
889
+ address: account3.address,
890
+ xpub: account3.xpub,
891
+ xprv: '',
892
+ isImported: false,
893
+ isTrezorWallet: false,
894
+ isLedgerWallet: false,
895
+ balances: { syscoin: 0, ethereum: 0 },
896
+ assets: { syscoin: [], ethereum: [] },
897
+ };
898
+
899
+ // BUG TEST: EVM accounts should have generic labels, not network-specific ones
900
+ expect(account1.label).toBe('Account 1'); // Should be generic
901
+ expect(account2.label).toBe('Account 2'); // Should be generic
902
+ expect(account3.label).toBe('Account 3'); // Should be generic
903
+
904
+ // These should NOT happen (network-specific labels for EVM accounts)
905
+ expect(account1.label).not.toContain('NEVM');
906
+ expect(account1.label).not.toContain('Testnet');
907
+ expect(account2.label).not.toContain('NEVM');
908
+ expect(account2.label).not.toContain('Testnet');
909
+ expect(account3.label).not.toContain('NEVM');
910
+ expect(account3.label).not.toContain('Testnet');
911
+
912
+ // Verify accounts work across EVM networks (same slip44=60)
913
+ expect(account1.address.startsWith('0x')).toBe(true);
914
+ expect(account2.address.startsWith('0x')).toBe(true);
915
+ expect(account3.address.startsWith('0x')).toBe(true);
916
+ });
917
+
918
+ it('should create UTXO accounts with network-specific labels', async () => {
919
+ // Test the keyring manager's actual label generation logic
920
+
921
+ // Set up empty Syscoin Testnet vault state (no pre-existing accounts)
922
+ const sysTestnetVaultState = createMockVaultState({
923
+ activeAccountId: 0,
924
+ activeAccountType: KeyringAccountType.HDAccount,
925
+ networkType: INetworkType.Syscoin,
926
+ chainId: 5700, // Syscoin UTXO Testnet
927
+ });
928
+
929
+ // Clear pre-existing accounts to let keyring manager create them
930
+ sysTestnetVaultState.accounts[KeyringAccountType.HDAccount] = {};
931
+
932
+ const sysTestnetVaultGetter = jest.fn(() => sysTestnetVaultState);
933
+
934
+ // Create keyring - this should call createFirstAccount internally
935
+ const sysKeyring = await KeyringManager.createInitialized(
936
+ PEACE_SEED_PHRASE,
937
+ FAKE_PASSWORD,
938
+ sysTestnetVaultGetter
939
+ );
940
+
941
+ // The first account should be created by keyring manager's createFirstAccount method
942
+ const firstAccount = await sysKeyring.createFirstAccount();
943
+
944
+ // Add to vault state so addNewAccount can find existing accounts for ID calculation
945
+ sysTestnetVaultState.accounts[KeyringAccountType.HDAccount][0] = {
946
+ id: 0,
947
+ label: firstAccount.label,
948
+ address: firstAccount.address,
949
+ xpub: firstAccount.xpub,
950
+ xprv: '',
951
+ isImported: false,
952
+ isTrezorWallet: false,
953
+ isLedgerWallet: false,
954
+ balances: { syscoin: 0, ethereum: 0 },
955
+ assets: { syscoin: [], ethereum: [] },
956
+ };
957
+
958
+ // Create second account using addNewAccount
959
+ const secondAccount = await sysKeyring.addNewAccount();
960
+
961
+ // Test that the keyring manager generates network-specific labels for UTXO accounts
962
+ expect(firstAccount.label).toContain('SYS'); // Should be "SYS-T 1" for testnet
963
+ expect(secondAccount.label).toContain('SYS'); // Should be "SYS-T 2" for testnet
964
+
965
+ // Verify accounts are UTXO format
966
+ expect(firstAccount.address.startsWith('tsys1')).toBe(true); // Testnet format
967
+ expect(secondAccount.address.startsWith('tsys1')).toBe(true); // Testnet format
968
+ });
969
+ });
970
+ });