@sidhujag/sysweb3-keyring 2.1.2 → 2.1.4

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 (148) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +55 -47
  3. package/cjs/current-vault-refusal.js +6 -0
  4. package/cjs/current-vault-refusal.js.map +1 -0
  5. package/cjs/evm-fees.js +29 -1
  6. package/cjs/evm-fees.js.map +1 -1
  7. package/cjs/hardware-transaction-authority.js +36 -0
  8. package/cjs/hardware-transaction-authority.js.map +1 -0
  9. package/cjs/index.js +5 -5
  10. package/cjs/index.js.map +1 -1
  11. package/cjs/keyring-core.js +466 -250
  12. package/cjs/keyring-core.js.map +1 -1
  13. package/cjs/keyring-host.js +113 -0
  14. package/cjs/keyring-host.js.map +1 -0
  15. package/cjs/keyring-manager.js +15 -55
  16. package/cjs/keyring-manager.js.map +1 -1
  17. package/cjs/ledger/host-types.js.map +1 -1
  18. package/cjs/ledger/index.js +44 -19
  19. package/cjs/ledger/index.js.map +1 -1
  20. package/cjs/providers.js +3 -1
  21. package/cjs/providers.js.map +1 -1
  22. package/cjs/software.js +10 -43
  23. package/cjs/software.js.map +1 -1
  24. package/cjs/storage-core.js +44 -4
  25. package/cjs/storage-core.js.map +1 -1
  26. package/cjs/transactions/ethereum.js +358 -294
  27. package/cjs/transactions/ethereum.js.map +1 -1
  28. package/cjs/transactions/evm-cancellation.js +43 -0
  29. package/cjs/transactions/evm-cancellation.js.map +1 -0
  30. package/cjs/transactions/evm-local-signer.js +15 -0
  31. package/cjs/transactions/evm-local-signer.js.map +1 -1
  32. package/cjs/transactions/evm-submission.js +61 -65
  33. package/cjs/transactions/evm-submission.js.map +1 -1
  34. package/cjs/transactions/hardware-psbt.js +11 -8
  35. package/cjs/transactions/hardware-psbt.js.map +1 -1
  36. package/cjs/transactions/index.js +10 -2
  37. package/cjs/transactions/index.js.map +1 -1
  38. package/cjs/transactions/prepared-evm.js +10 -7
  39. package/cjs/transactions/prepared-evm.js.map +1 -1
  40. package/cjs/transactions/syscoin-core.js +282 -202
  41. package/cjs/transactions/syscoin-core.js.map +1 -1
  42. package/cjs/trezor/connect.js +19 -2
  43. package/cjs/trezor/connect.js.map +1 -1
  44. package/cjs/trezor/index.js +38 -16
  45. package/cjs/trezor/index.js.map +1 -1
  46. package/cjs/types.js.map +1 -1
  47. package/cjs/utils/owned-blockbook-account.js +45 -0
  48. package/cjs/utils/owned-blockbook-account.js.map +1 -0
  49. package/esm/current-vault-refusal.js +3 -0
  50. package/esm/current-vault-refusal.js.map +1 -0
  51. package/esm/evm-fees.js +242 -0
  52. package/esm/evm-fees.js.map +1 -0
  53. package/esm/hardware-transaction-authority.js +33 -0
  54. package/esm/hardware-transaction-authority.js.map +1 -0
  55. package/esm/initial-state.js +102 -0
  56. package/esm/initial-state.js.map +1 -0
  57. package/esm/keyring-core.js +2781 -0
  58. package/esm/keyring-core.js.map +1 -0
  59. package/esm/keyring-host.js +108 -0
  60. package/esm/keyring-host.js.map +1 -0
  61. package/esm/ledger/dmk-host.js +31 -0
  62. package/esm/ledger/dmk-host.js.map +1 -0
  63. package/esm/ledger/dmk-selected-host.js +6 -0
  64. package/esm/ledger/dmk-selected-host.js.map +1 -0
  65. package/esm/ledger/dmk-session.js +154 -34
  66. package/esm/ledger/dmk-session.js.map +1 -1
  67. package/esm/ledger/dmk-utxo.js +9 -3
  68. package/esm/ledger/dmk-utxo.js.map +1 -1
  69. package/esm/ledger/host-types.js.map +1 -1
  70. package/esm/ledger/index.js +44 -19
  71. package/esm/ledger/index.js.map +1 -1
  72. package/esm/ledger-host.js +3 -27
  73. package/esm/ledger-host.js.map +1 -1
  74. package/esm/network-utils.js +70 -0
  75. package/esm/network-utils.js.map +1 -0
  76. package/esm/providers.js +3 -1
  77. package/esm/providers.js.map +1 -1
  78. package/esm/software-hd-account.js +20 -0
  79. package/esm/software-hd-account.js.map +1 -0
  80. package/esm/storage-core.js +258 -0
  81. package/esm/storage-core.js.map +1 -0
  82. package/esm/transactions/ethereum.js +2753 -0
  83. package/esm/transactions/ethereum.js.map +1 -0
  84. package/esm/transactions/evm-cancellation.js +40 -0
  85. package/esm/transactions/evm-cancellation.js.map +1 -0
  86. package/esm/transactions/evm-local-signer.js +202 -0
  87. package/esm/transactions/evm-local-signer.js.map +1 -0
  88. package/esm/transactions/evm-submission.js +187 -0
  89. package/esm/transactions/evm-submission.js.map +1 -0
  90. package/esm/transactions/prepared-evm.js +194 -0
  91. package/esm/transactions/prepared-evm.js.map +1 -0
  92. package/esm/transactions/syscoin-core.js +691 -0
  93. package/esm/transactions/syscoin-core.js.map +1 -0
  94. package/esm/trezor/connect.js +19 -2
  95. package/esm/trezor/connect.js.map +1 -1
  96. package/esm/trezor/index.js +38 -16
  97. package/esm/trezor/index.js.map +1 -1
  98. package/esm/types.js.map +1 -1
  99. package/esm/utils/excluded-utxos.js +53 -0
  100. package/esm/utils/excluded-utxos.js.map +1 -0
  101. package/esm/utils/owned-blockbook-account.js +9 -0
  102. package/esm/utils/owned-blockbook-account.js.map +1 -0
  103. package/esm/utils/psbt.js +32 -0
  104. package/esm/utils/psbt.js.map +1 -0
  105. package/esm/utils/watch-descriptor.js +138 -0
  106. package/esm/utils/watch-descriptor.js.map +1 -0
  107. package/package.json +17 -11
  108. package/types/current-vault-refusal.d.ts +2 -0
  109. package/types/evm-fees.d.ts +25 -0
  110. package/types/hardware-transaction-authority.d.ts +5 -0
  111. package/types/index.d.ts +3 -3
  112. package/types/keyring-core.d.ts +48 -44
  113. package/types/keyring-host.d.ts +46 -0
  114. package/types/keyring-manager.d.ts +14 -7
  115. package/types/ledger/dmk-host.d.ts +7 -0
  116. package/types/ledger/dmk-selected-host.d.ts +7 -0
  117. package/types/ledger/dmk-session.d.ts +10 -2
  118. package/types/ledger/host-types.d.ts +4 -1
  119. package/types/ledger/index.d.ts +1 -1
  120. package/types/ledger/types.d.ts +3 -2
  121. package/types/ledger-host.d.ts +6 -2
  122. package/types/providers.d.ts +1 -1
  123. package/types/software.d.ts +7 -19
  124. package/types/storage-core.d.ts +3 -1
  125. package/types/transactions/ethereum.d.ts +14 -11
  126. package/types/transactions/evm-cancellation.d.ts +10 -0
  127. package/types/transactions/evm-local-signer.d.ts +2 -2
  128. package/types/transactions/evm-submission.d.ts +11 -4
  129. package/types/transactions/hardware-psbt.d.ts +1 -1
  130. package/types/transactions/index.d.ts +3 -2
  131. package/types/transactions/prepared-evm.d.ts +4 -1
  132. package/types/transactions/syscoin-core.d.ts +48 -21
  133. package/types/trezor/connect.d.ts +6 -3
  134. package/types/trezor/index.d.ts +4 -3
  135. package/types/types.d.ts +95 -63
  136. package/types/utils/owned-blockbook-account.d.ts +3 -0
  137. package/cjs/hardware-wallet-manager-singleton.js +0 -34
  138. package/cjs/hardware-wallet-manager-singleton.js.map +0 -1
  139. package/cjs/storage.js +0 -20
  140. package/cjs/storage.js.map +0 -1
  141. package/cjs/transactions/syscoin.js +0 -16
  142. package/cjs/transactions/syscoin.js.map +0 -1
  143. package/cjs/utils/blockbook-cache.js +0 -76
  144. package/cjs/utils/blockbook-cache.js.map +0 -1
  145. package/types/hardware-wallet-manager-singleton.d.ts +0 -19
  146. package/types/storage.d.ts +0 -5
  147. package/types/transactions/syscoin.d.ts +0 -9
  148. package/types/utils/blockbook-cache.d.ts +0 -6
@@ -32,9 +32,21 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
36
+ if (kind === "m") throw new TypeError("Private method is not writable");
37
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
38
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
39
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
40
+ };
41
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
42
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
43
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
44
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
45
+ };
35
46
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
47
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
48
  };
49
+ var _KeyringCore_instances, _KeyringCore_deviceUnlockRetired, _KeyringCore_sessionOwnership, _KeyringCore_createDeviceUnlockControls, _KeyringCore_acceptSessionFrom, _KeyringCore_assertSessionOwner, _KeyringCore_captureSessionGuard, _KeyringCore_replaceSessionBuffers, _KeyringCore_finishUnlock, _KeyringCore_installVaultSession, _KeyringCore_recreateSessionFromVault;
38
50
  Object.defineProperty(exports, "__esModule", { value: true });
39
51
  exports.KeyringCore = void 0;
40
52
  const secp256k1_1 = __importDefault(require("@bitcoinerlab/secp256k1"));
@@ -56,8 +68,10 @@ const SLH_DSA_DERIVATION_VERSION = 1;
56
68
  // Pali expands this setup secret into the 48-byte SLH skSeed/skPrf/pkSeed tuple.
57
69
  const SLH_DSA_SETUP_SECRET_HEX_LENGTH = 64;
58
70
  const SLH_DSA_PARAMETER_SET = 'SLH-DSA-SHA2-128-24';
71
+ const current_vault_refusal_1 = require("./current-vault-refusal");
59
72
  const ethers_v6_1 = require("./ethers-v6");
60
73
  const initial_state_1 = require("./initial-state");
74
+ const keyring_host_1 = require("./keyring-host");
61
75
  const private_records_1 = require("./private-records");
62
76
  const signers_1 = require("./signers");
63
77
  const software_hd_account_1 = require("./software-hd-account");
@@ -67,10 +81,32 @@ const evm_local_signer_1 = require("./transactions/evm-local-signer");
67
81
  const prepared_evm_1 = require("./transactions/prepared-evm");
68
82
  const syscoin_core_1 = require("./transactions/syscoin-core");
69
83
  const types_1 = require("./types");
70
- const blockbook_cache_1 = require("./utils/blockbook-cache");
71
84
  const derivation_paths_1 = require("./utils/derivation-paths");
85
+ const owned_blockbook_account_1 = require("./utils/owned-blockbook-account");
72
86
  const psbt_account_scope_1 = require("./utils/psbt-account-scope");
73
87
  const watch_descriptor_1 = require("./utils/watch-descriptor");
88
+ const currentVaultSalt = (keys) => {
89
+ const salt = keys && typeof keys === 'object' && Reflect.ownKeys(keys).length === 1
90
+ ? Object.getOwnPropertyDescriptor(keys, 'salt')?.value
91
+ : undefined;
92
+ return typeof salt === 'string' && /^[0-9a-f]{32}$/.test(salt)
93
+ ? salt
94
+ : undefined;
95
+ };
96
+ const readCurrentVaultPair = async (readPair) => {
97
+ const pair = await readPair();
98
+ if (pair === null)
99
+ return null;
100
+ const { vault, keys, pairFingerprint } = pair;
101
+ const salt = currentVaultSalt(keys);
102
+ if (typeof vault !== 'string' ||
103
+ salt === undefined ||
104
+ typeof pairFingerprint !== 'string' ||
105
+ !/^[0-9a-f]{64}$/.test(pairFingerprint)) {
106
+ throw new Error('Invalid device unlock vault pair');
107
+ }
108
+ return { vault, salt, pairFingerprint };
109
+ };
74
110
  // Dynamic ETH HD path generation - will be computed as needed
75
111
  /**
76
112
  * Secure Buffer implementation for sensitive data
@@ -111,7 +147,8 @@ class SecureBuffer {
111
147
  }
112
148
  }
113
149
  class KeyringCore {
114
- constructor(dependencies) {
150
+ constructor(binding, hardware) {
151
+ _KeyringCore_instances.add(this);
115
152
  // Store getter function for accessing Redux state
116
153
  this.getVaultState = null;
117
154
  // Method to inject store getter from Pali side
@@ -156,7 +193,9 @@ class KeyringCore {
156
193
  this.sessionPassword = null;
157
194
  this.sessionMnemonic = null; // can be a mnemonic or a zprv, can be changed to a zprv when using an imported wallet
158
195
  this.privateRecordSessionRevision = 0;
159
- this.createSyscoinTransactions = () => new syscoin_core_1.SyscoinTransactionsCore(this.getSigner, this.getReadOnlySigner, this.getAccountsState, this.getAddress, this.syscoinHardwareSigners, this.capturePsbtSigningContext);
196
+ _KeyringCore_deviceUnlockRetired.set(this, false);
197
+ _KeyringCore_sessionOwnership.set(this, void 0);
198
+ this.createSyscoinTransactions = () => new syscoin_core_1.SyscoinTransactionsCore(this.getSigner, this.getReadOnlySigner, this.getAccountsState, this.syscoinHardwareSigners, this.capturePsbtSigningContext);
160
199
  this.requireLedgerSigner = () => {
161
200
  if (!this.ledgerSigner)
162
201
  throw (0, syscoin_core_1.createHardwareSignerUnavailableError)('Ledger');
@@ -168,12 +207,6 @@ class KeyringCore {
168
207
  return this.trezorSigner;
169
208
  };
170
209
  // ===================================== PUBLIC METHODS - KEYRING MANAGER FOR HD - SYS ALL ===================================== //
171
- this.setStorage = (client) => {
172
- if (!this.storage.setClient) {
173
- throw new Error('Storage replacement is unavailable in this host');
174
- }
175
- this.storage.setClient(client);
176
- };
177
210
  this.validateAccountType = (account) => {
178
211
  return account.isImported === true
179
212
  ? types_1.KeyringAccountType.Imported
@@ -191,6 +224,11 @@ class KeyringCore {
191
224
  this.sessionMnemonic.clear();
192
225
  this.sessionMnemonic = null;
193
226
  }
227
+ const ownership = __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f");
228
+ if (ownership.tracker.current === ownership.token) {
229
+ ownership.tracker.current = undefined;
230
+ ownership.tracker.epoch++;
231
+ }
194
232
  // Clear transaction handlers that may hold HD signers
195
233
  if (this.syscoinTransaction) {
196
234
  // Replace with empty object to clear references
@@ -234,40 +272,13 @@ class KeyringCore {
234
272
  if (!this.sessionPassword || !this.sessionMnemonic) {
235
273
  throw new Error('Session data is missing during transfer');
236
274
  }
275
+ __classPrivateFieldGet(targetKeyring, _KeyringCore_instances, "m", _KeyringCore_acceptSessionFrom).call(targetKeyring, this);
237
276
  this.privateRecordSessionRevision++;
238
- targetKeyring.acceptSessionFrom(this);
277
+ __classPrivateFieldSet(this, _KeyringCore_deviceUnlockRetired, true, "f");
239
278
  // Null out our references (do NOT clear buffers - target owns them now)
240
279
  this.sessionPassword = null;
241
280
  this.sessionMnemonic = null;
242
281
  };
243
- // The checked transfer path never dispatches through a public host receiver.
244
- this.acceptSessionFrom = (source) => {
245
- if (source.authority !== this.authority ||
246
- source.storage !== this.storage ||
247
- source.vaultPersistence !== this.vaultPersistence) {
248
- throw new Error('Cannot transfer session between different wallet authorities');
249
- }
250
- if (!source.sessionPassword || !source.sessionMnemonic) {
251
- throw new Error('Session data is missing during transfer');
252
- }
253
- this.replaceSessionBuffers(source.sessionPassword, source.sessionMnemonic);
254
- };
255
- // Legacy trusted-host API. SoftwareKeyring disables direct buffer injection.
256
- this.receiveSessionOwnership = (sessionPassword, sessionMnemonic) => this.replaceSessionBuffers(sessionPassword, sessionMnemonic);
257
- this.replaceSessionBuffers = (sessionPassword, sessionMnemonic) => {
258
- this.privateRecordSessionRevision++;
259
- // Clear any existing data first
260
- if (this.sessionPassword) {
261
- this.sessionPassword.clear();
262
- }
263
- if (this.sessionMnemonic) {
264
- this.sessionMnemonic.clear();
265
- }
266
- // Take ownership of the actual SecureBuffer objects
267
- // No copying - these are the original objects
268
- this.sessionPassword = sessionPassword;
269
- this.sessionMnemonic = sessionMnemonic;
270
- };
271
282
  this.addNewAccount = async (label) => {
272
283
  // Check if wallet is unlocked
273
284
  if (!this.isUnlocked()) {
@@ -314,91 +325,41 @@ class KeyringCore {
314
325
  xpub: derived.address,
315
326
  };
316
327
  };
317
- this.getNewChangeAddress = async () => {
318
- const vault = this.getVault();
319
- const { accounts, activeAccount } = vault;
320
- const account = accounts[activeAccount.type]?.[activeAccount.id];
321
- if (!account) {
322
- throw new Error('Active account not found');
323
- }
324
- const { xpub, isImported, address } = account;
325
- // For imported single-address accounts, always return the single address
326
- const looksLikeSingleAddress = isImported &&
327
- xpub === address &&
328
- !this.isDescriptor(xpub) &&
329
- !this.isXpubLike(xpub);
330
- if (looksLikeSingleAddress)
331
- return address;
332
- return await this.getAddress(xpub, true); // Don't skip increment - get next unused
333
- };
334
- this.getChangeAddress = async (id) => {
335
- const vault = this.getVault();
336
- const { accounts, activeAccount } = vault;
337
- const account = accounts[activeAccount.type]?.[id];
338
- if (!account) {
339
- throw new Error(`Account with id ${id} not found`);
340
- }
341
- const { xpub, isImported, address } = account;
342
- if (isImported &&
343
- xpub === address &&
344
- !this.isDescriptor(xpub) &&
345
- !this.isXpubLike(xpub))
346
- return address;
347
- return await this.getAddress(xpub, true);
348
- };
349
- this.getPubkey = async (id, isChangeAddress) => {
350
- const vault = this.getVault();
351
- const { accounts, activeAccount } = vault;
352
- const account = accounts[activeAccount.type]?.[id];
353
- if (!account) {
354
- throw new Error(`Account with id ${id} not found`);
355
- }
356
- const { xpub, isImported, address } = account;
357
- // Guard: single-address imported are watch-only
358
- if (isImported && xpub === address) {
359
- throw new Error('Public key not available for single-address imported accounts');
360
- }
361
- // Guard: descriptor/xpub watch-only (no xprv and not hardware)
362
- if (this.isWatchOnlyAccount(account)) {
363
- throw new Error('Public key not available for watch-only accounts');
364
- }
365
- return await this.getCurrentAddressPubkey(xpub, isChangeAddress);
366
- };
367
- this.getBip32Path = async (id, isChangeAddress) => {
368
- const vault = this.getVault();
369
- const { accounts, activeAccount } = vault;
370
- const account = accounts[activeAccount.type]?.[id];
371
- if (!account) {
372
- throw new Error(`Account with id ${id} not found`);
373
- }
374
- const { xpub, isImported, address } = account;
375
- // Guard: single-address imported are watch-only
376
- if (isImported && xpub === address) {
377
- throw new Error('BIP32 path not available for single-address imported accounts');
378
- }
379
- // Guard: descriptor/xpub watch-only (no xprv and not hardware)
380
- if (this.isWatchOnlyAccount(account)) {
381
- throw new Error('BIP32 path not available for watch-only accounts');
382
- }
383
- return await this.getCurrentAddressBip32Path(xpub, isChangeAddress);
384
- };
385
- this.updateReceivingAddress = async () => {
386
- const vault = this.getVault();
387
- const { accounts, activeAccount } = vault;
388
- const account = accounts[activeAccount.type]?.[activeAccount.id];
389
- if (!account) {
390
- throw new Error('Active account not found');
391
- }
328
+ this.getActiveUtxoAddressAccount = (id, ownedRead) => {
329
+ const captured = (0, syscoin_core_1.captureOwnedSyscoinRead)(ownedRead, this.getAccountsState, this.capturePsbtSigningContext);
330
+ if (id !== captured.accountId)
331
+ throw new Error('UTXO account is not active');
332
+ const account = this.getVault().accounts[captured.accountType]?.[id];
333
+ captured.read.assertCurrent();
334
+ return { account, read: captured.read };
335
+ };
336
+ this.getNewChangeAddress = (ownedRead) => this.getChangeAddress(this.getVault().activeAccount.id, ownedRead);
337
+ this.getChangeAddress = async (id, ownedRead) => {
338
+ const { account, read } = this.getActiveUtxoAddressAccount(id, ownedRead);
339
+ return this.getAddressForUtxoAccount(account, true, read);
340
+ };
341
+ this.getPubkey = async (id, isChangeAddress, ownedRead) => {
342
+ const { account, read } = this.getActiveUtxoAddressAccount(id, ownedRead);
343
+ this.assertUtxoDerivationAvailable(account, 'Public key');
344
+ return this.getAddressPubkey(isChangeAddress, read);
345
+ };
346
+ this.getBip32Path = async (id, isChangeAddress, ownedRead) => {
347
+ const { account, read } = this.getActiveUtxoAddressAccount(id, ownedRead);
348
+ this.assertUtxoDerivationAvailable(account, 'BIP32 path');
349
+ return this.getAddressBip32Path(id, isChangeAddress, read);
350
+ };
351
+ this.updateReceivingAddress = async (ownedRead) => {
352
+ const { account, read } = this.getActiveUtxoAddressAccount(this.getVault().activeAccount.id, ownedRead);
353
+ return this.getAddressForUtxoAccount(account, false, read);
354
+ };
355
+ this.getAddressForUtxoAccount = (account, change, read) => {
392
356
  const { xpub, isImported, address } = account;
393
357
  if (isImported &&
394
358
  xpub === address &&
395
359
  !this.isDescriptor(xpub) &&
396
360
  !this.isXpubLike(xpub))
397
- return address;
398
- const nextAddress = await this.getAddress(xpub, false);
399
- // NOTE: Address updates should be dispatched to Redux store, not updated here
400
- // The calling code should handle the Redux dispatch
401
- return nextAddress;
361
+ return Promise.resolve(address);
362
+ return this.discoverUtxoAddress(change, read);
402
363
  };
403
364
  this.getAccountById = (id, accountType) => {
404
365
  const vault = this.getVault();
@@ -676,62 +637,56 @@ class KeyringCore {
676
637
  };
677
638
  this.getNetwork = () => this.getVault().activeNetwork;
678
639
  this.createEthAccount = (privateKey) => (0, evm_local_signer_1.privateKeyToAccount)(privateKey);
679
- // Helper to get current account data from backend
680
- this.fetchCurrentAccountData = async (xpub, isChangeAddress) => {
681
- const vault = this.getVault();
682
- const { activeNetwork } = vault;
683
- // Use read-only signer for backend calls - works for all account types including hardware wallets
684
- const { main } = this.getReadOnlySigner();
685
- const options = 'tokens=used&details=tokens';
686
- // In-flight deduplicated: concurrent callers within a send flow
687
- // (fee estimation, PSBT creation, signing) share a single request
688
- const { tokens } = await (0, blockbook_cache_1.fetchBackendAccountCached)(main.blockbookURL, xpub, options);
640
+ this.fetchCurrentAccountData = async (isChangeAddress, read) => {
641
+ read.assertCurrent();
642
+ const { tokens } = await (0, owned_blockbook_account_1.fetchOwnedBlockbookAccount)(read.network.url, read.xpub, read.transport);
643
+ read.assertCurrent();
689
644
  const { receivingIndex, changeIndex } = this.setLatestIndexesFromXPubTokens(tokens);
690
- // Get network configuration for BIP84 using network-provided pub type macros
691
- const networkConfig = (0, sysweb3_network_1.getNetworkConfig)(activeNetwork.slip44, activeNetwork.currency);
692
- const pubTypes = networkConfig?.types?.zPubType;
693
- if (!pubTypes) {
694
- throw new Error('Missing zPubType in network configuration');
695
- }
696
- const networks = networkConfig.networks;
697
- const currentAccount = new BIP84.fromZPub(xpub, pubTypes, networks);
698
- const addressIndex = isChangeAddress ? changeIndex : receivingIndex;
699
645
  return {
700
- currentAccount,
701
- addressIndex,
702
- main,
646
+ currentAccount: this.loadBip84AddressAccount(read.xpub, read.network),
647
+ addressIndex: isChangeAddress ? changeIndex : receivingIndex,
703
648
  };
704
649
  };
705
- this.getAddress = async (xpub, isChangeAddress, options) => {
706
- if (this.isDescriptor(xpub)) {
707
- // The BIP84 history scanner below cannot choose a descriptor's next
708
- // unused index. Import can derive index zero locally, but rotation must
709
- // not reinterpret a descriptor as a BIP84 extended key.
710
- if (!options?.forceIndex0) {
711
- throw new Error('Address rotation is not supported for watch-only descriptors');
712
- }
713
- return (0, watch_descriptor_1.deriveDescriptorAddress)(xpub, this.getVault().activeNetwork, {
714
- change: isChangeAddress,
715
- });
716
- }
717
- const { currentAccount, addressIndex } = await this.fetchCurrentAccountData(xpub, isChangeAddress);
718
- const effectiveIndex = options?.forceIndex0 ? 0 : addressIndex;
719
- const address = currentAccount.getAddress(effectiveIndex, isChangeAddress, 84);
720
- return address;
721
- };
722
- this.getCurrentAddressPubkey = async (xpub, isChangeAddress) => {
723
- const { currentAccount, addressIndex } = await this.fetchCurrentAccountData(xpub, isChangeAddress);
724
- // BIP84 returns the public key as a hex string directly
650
+ this.discoverUtxoAddress = async (isChangeAddress, read) => {
651
+ read.assertCurrent();
652
+ if (this.isDescriptor(read.xpub))
653
+ throw new Error('Address rotation is not supported for watch-only descriptors');
654
+ const { currentAccount, addressIndex } = await this.fetchCurrentAccountData(isChangeAddress, read);
655
+ read.assertCurrent();
656
+ return currentAccount.getAddress(addressIndex, isChangeAddress, 84);
657
+ };
658
+ this.getAddress = async (xpub, isChangeAddress, ownedRead) => {
659
+ const id = this.getVault().activeAccount.id;
660
+ const { account, read } = this.getActiveUtxoAddressAccount(id, ownedRead);
661
+ if (xpub !== read.xpub)
662
+ throw new Error('UTXO account is not active');
663
+ return this.getAddressForUtxoAccount(account, isChangeAddress, read);
664
+ };
665
+ this.getAddressPubkey = async (isChangeAddress, read) => {
666
+ const { currentAccount, addressIndex } = await this.fetchCurrentAccountData(isChangeAddress, read);
667
+ read.assertCurrent();
725
668
  return currentAccount.getPublicKey(addressIndex, isChangeAddress);
726
669
  };
727
- this.getCurrentAddressBip32Path = async (xpub, isChangeAddress) => {
728
- const vault = this.getVault();
729
- const { activeAccount, activeNetwork } = vault;
730
- const { addressIndex } = await this.fetchCurrentAccountData(xpub, isChangeAddress);
731
- // Use the utility function to generate the proper derivation path
732
- const coinShortcut = activeNetwork.currency.toLowerCase(); // e.g., 'sys', 'btc'
733
- const path = (0, derivation_paths_1.getAddressDerivationPath)(coinShortcut, activeNetwork.slip44, activeAccount.id, isChangeAddress, addressIndex);
734
- return path;
670
+ this.getCurrentAddressPubkey = async (xpub, isChangeAddress, ownedRead) => {
671
+ const id = this.getVault().activeAccount.id;
672
+ const { account, read } = this.getActiveUtxoAddressAccount(id, ownedRead);
673
+ if (xpub !== read.xpub)
674
+ throw new Error('UTXO account is not active');
675
+ this.assertUtxoDerivationAvailable(account, 'Public key');
676
+ return this.getAddressPubkey(isChangeAddress, read);
677
+ };
678
+ this.getAddressBip32Path = async (id, isChangeAddress, read) => {
679
+ const { addressIndex } = await this.fetchCurrentAccountData(isChangeAddress, read);
680
+ read.assertCurrent();
681
+ return (0, derivation_paths_1.getAddressDerivationPath)(read.network.currency.toLowerCase(), read.network.slip44, id, isChangeAddress, addressIndex);
682
+ };
683
+ this.getCurrentAddressBip32Path = async (xpub, isChangeAddress, ownedRead) => {
684
+ const id = this.getVault().activeAccount.id;
685
+ const { account, read } = this.getActiveUtxoAddressAccount(id, ownedRead);
686
+ if (xpub !== read.xpub)
687
+ throw new Error('UTXO account is not active');
688
+ this.assertUtxoDerivationAvailable(account, 'BIP32 path');
689
+ return this.getAddressBip32Path(id, isChangeAddress, read);
735
690
  };
736
691
  this.logout = () => {
737
692
  this.lockWallet();
@@ -817,8 +772,9 @@ class KeyringCore {
817
772
  throw error;
818
773
  }
819
774
  };
820
- this.capturePsbtSigningContext = () => {
775
+ this.capturePsbtSigningContext = (bindSessionRevision = true) => {
821
776
  const session = this.sessionPassword;
777
+ const revision = this.privateRecordSessionRevision;
822
778
  const vault = this.getVault();
823
779
  const selected = { ...vault.activeAccount };
824
780
  const account = vault.accounts[selected.type]?.[selected.id];
@@ -830,6 +786,8 @@ class KeyringCore {
830
786
  if (!session ||
831
787
  session.isCleared() ||
832
788
  this.sessionPassword !== session ||
789
+ (bindSessionRevision &&
790
+ this.privateRecordSessionRevision !== revision) ||
833
791
  current.activeAccount.id !== selected.id ||
834
792
  current.activeAccount.type !== selected.type ||
835
793
  currentAccount?.address !== identity.address ||
@@ -932,22 +890,32 @@ class KeyringCore {
932
890
  };
933
891
  };
934
892
  // Read-only version that works when wallet is locked
935
- this.getReadOnlySigner = () => {
936
- if (this.getActiveChain() !== sysweb3_network_1.INetworkType.Syscoin) {
893
+ this.getReadOnlySigner = (ownedRead) => {
894
+ ownedRead?.assertCurrent();
895
+ if ((ownedRead ? ownedRead.network.kind : this.getActiveChain()) !==
896
+ sysweb3_network_1.INetworkType.Syscoin) {
937
897
  throw new Error('Switch to UTXO chain');
938
898
  }
939
899
  // Create syscoinjs instance without HD signer for read-only operations
940
- const vault = this.getVault();
941
- const network = vault.activeNetwork;
900
+ const network = ownedRead?.network ?? this.getVault().activeNetwork;
942
901
  const networkConfig = (0, sysweb3_network_1.getNetworkConfig)(network.slip44, network.currency);
943
902
  const isTestnet = network.slip44 === 1;
944
903
  const bitcoinjsNetwork = isTestnet
945
904
  ? networkConfig?.networks?.testnet
946
905
  : networkConfig?.networks?.mainnet;
906
+ if (ownedRead && !bitcoinjsNetwork)
907
+ throw new Error('Missing owned UTXO network configuration');
947
908
  const syscoinMainSigner = new syscoinjs.SyscoinJSLib(null, // No HD signer needed for read-only operations
948
- network.url, bitcoinjsNetwork);
909
+ network.url, bitcoinjsNetwork, ...(ownedRead ? [ownedRead.transport] : []));
910
+ if (!ownedRead)
911
+ return { main: syscoinMainSigner };
949
912
  return {
950
913
  main: syscoinMainSigner,
914
+ getOwnedAddress: (xpub, isChangeAddress) => {
915
+ if (xpub !== ownedRead.xpub)
916
+ throw new Error('UTXO account is not active');
917
+ return this.discoverUtxoAddress(isChangeAddress, ownedRead);
918
+ },
951
919
  };
952
920
  };
953
921
  this.getAccountsState = () => {
@@ -1139,10 +1107,13 @@ class KeyringCore {
1139
1107
  if (!BIP84.validateMnemonic(seedPhrase)) {
1140
1108
  throw new Error('Invalid Seed');
1141
1109
  }
1110
+ const assertCurrent = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
1111
+ assertCurrent();
1142
1112
  const [vault, vaultKeys] = await Promise.all([
1143
1113
  this.storage.get('vault'),
1144
1114
  this.storage.get('vault-keys'),
1145
1115
  ]);
1116
+ assertCurrent();
1146
1117
  const hasVault = vault !== undefined && vault !== null;
1147
1118
  const hasVaultKeys = vaultKeys !== undefined && vaultKeys !== null;
1148
1119
  const livePassword = this.sessionPassword;
@@ -1157,6 +1128,7 @@ class KeyringCore {
1157
1128
  throw new Error('Cannot initialize a new vault over existing wallet storage');
1158
1129
  }
1159
1130
  const sessionPasswordSaltedHash = await this.deriveStoredSessionKey(password, vaultKeys);
1131
+ assertCurrent();
1160
1132
  if (livePassword === this.sessionPassword &&
1161
1133
  liveMnemonic === this.sessionMnemonic &&
1162
1134
  sessionPasswordSaltedHash === this.getSessionPasswordString()) {
@@ -1172,6 +1144,7 @@ class KeyringCore {
1172
1144
  }
1173
1145
  if (currentMnemonic === seedPhrase) {
1174
1146
  const storedVault = await this.vaultPersistence.getDecryptedVault(sessionPasswordSaltedHash);
1147
+ assertCurrent();
1175
1148
  if (livePassword === this.sessionPassword &&
1176
1149
  liveMnemonic === this.sessionMnemonic &&
1177
1150
  !livePassword.isCleared() &&
@@ -1197,8 +1170,10 @@ class KeyringCore {
1197
1170
  mnemonic: seedPhrase, // Store plain mnemonic - setEncryptedVault will encrypt the entire vault
1198
1171
  },
1199
1172
  // v4 vault is encrypted with the derived session password key (PBKDF2 output)
1200
- sessionPasswordSaltedHash, { salt });
1201
- await this.recreateSessionFromVault(sessionPasswordSaltedHash);
1173
+ sessionPasswordSaltedHash, { salt }, assertCurrent);
1174
+ assertCurrent();
1175
+ const assertInstalled = await __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_recreateSessionFromVault).call(this, sessionPasswordSaltedHash, assertCurrent);
1176
+ assertInstalled();
1202
1177
  };
1203
1178
  // NEW: Create first account without signer setup
1204
1179
  this.createFirstAccount = async (label) => {
@@ -1237,27 +1212,30 @@ class KeyringCore {
1237
1212
  await this.initializeSession(seedPhrase, password);
1238
1213
  return await this.createFirstAccount();
1239
1214
  };
1240
- if (!dependencies ||
1241
- typeof dependencies.authority !== 'object' ||
1242
- dependencies.authority === null) {
1243
- throw new TypeError('Keyring core requires an explicit wallet authority');
1244
- }
1245
- if (typeof dependencies.vaultPersistence?.getDecryptedVault !== 'function' ||
1246
- typeof dependencies.vaultPersistence?.setEncryptedVault !== 'function') {
1247
- throw new TypeError('Keyring core requires bound vault persistence');
1248
- }
1215
+ const dependencies = (0, keyring_host_1.takeKeyringHostBinding)(binding);
1249
1216
  this.authority = dependencies.authority;
1217
+ __classPrivateFieldSet(this, _KeyringCore_sessionOwnership, {
1218
+ tracker: dependencies.sessionOwner,
1219
+ token: Symbol(),
1220
+ }, "f");
1250
1221
  this.storage = dependencies.storage;
1251
1222
  this.vaultPersistence = dependencies.vaultPersistence;
1252
- this.ledgerSigner = dependencies.ledgerSigner;
1253
- this.trezorSigner = dependencies.trezorSigner;
1254
- this.syscoinHardwareSigners = { ...dependencies.syscoinHardwareSigners };
1223
+ this.ledgerSigner = hardware.ledgerSigner;
1224
+ this.trezorSigner = hardware.trezorSigner;
1225
+ this.syscoinHardwareSigners = { ...hardware.syscoinHardwareSigners };
1255
1226
  this.writeUtf8Diagnostic(false);
1256
1227
  this.utf8Error = false;
1257
1228
  this.initialTrezorAccountState = initial_state_1.initialActiveTrezorAccountState;
1258
1229
  this.initialLedgerAccountState = initial_state_1.initialActiveLedgerAccountState;
1259
1230
  this.syscoinTransaction = this.createSyscoinTransactions();
1260
1231
  this.ethereumTransaction = new ethereum_1.EthereumTransactions(this.getNetwork, this.getDecryptedPrivateKey, this.getAccountsState, this.ledgerSigner, this.trezorSigner, this.capturePsbtSigningContext, dependencies.captureEvmSubmission);
1232
+ if (dependencies.deviceUnlock) {
1233
+ const { port, receive } = dependencies.deviceUnlock;
1234
+ const readVaultPair = this.storage.readDeviceUnlockVaultPair;
1235
+ if (typeof readVaultPair !== 'function')
1236
+ throw new TypeError('Device unlock requires an atomic vault pair reader');
1237
+ receive(__classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_createDeviceUnlockControls).call(this, port, readVaultPair.bind(this.storage)));
1238
+ }
1261
1239
  }
1262
1240
  // Convenience method for complete setup after construction
1263
1241
  async initialize(seed, password, network) {
@@ -1277,18 +1255,31 @@ class KeyringCore {
1277
1255
  * not charge a failed password attempt for an operational error.
1278
1256
  */
1279
1257
  async unlock(password) {
1258
+ let assertCurrent = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
1280
1259
  let authenticated = false;
1281
1260
  try {
1261
+ assertCurrent();
1282
1262
  const vaultKeys = await this.storage.get('vault-keys');
1283
1263
  if (!vaultKeys) {
1264
+ assertCurrent();
1284
1265
  throw new Error('Vault keys not found. Reload the wallet and try again.');
1285
1266
  }
1286
1267
  // v4: Validate password by deriving the session key once and decrypting the vault with it.
1287
1268
  // This avoids an extra KDF pass just for a stored verifier.
1288
1269
  const { salt } = vaultKeys;
1270
+ const readVaultPair = this.storage.readDeviceUnlockVaultPair;
1271
+ const accountingSalt = readVaultPair
1272
+ ? currentVaultSalt(vaultKeys)
1273
+ : undefined;
1274
+ // Permit only an authenticated negative verdict for the same atomic
1275
+ // pair after cancellation. Legacy migration and positive results stay guarded.
1276
+ if (accountingSalt === undefined)
1277
+ assertCurrent();
1289
1278
  // Derive session encryption key (in-memory only) used to encrypt/decrypt xprv fields
1290
1279
  // IMPORTANT: This MUST NOT be the same value as the stored verifier above.
1291
- let sessionPasswordKey = await this.deriveStoredSessionKey(password, vaultKeys);
1280
+ let sessionPasswordKey = await this.deriveStoredSessionKey(password, accountingSalt === undefined ? vaultKeys : { salt: accountingSalt });
1281
+ if (accountingSalt === undefined)
1282
+ assertCurrent();
1292
1283
  // Handle migration from old vault format with currentSessionSalt
1293
1284
  if (vaultKeys.currentSessionSalt) {
1294
1285
  console.log('[KeyringManager] Detected old vault format, handling session migration...');
@@ -1299,6 +1290,7 @@ class KeyringCore {
1299
1290
  // derived key. Selecting by stored format also permits a safe retry
1300
1291
  // if the vault write succeeded but the metadata write was interrupted.
1301
1292
  const { mnemonic } = await this.vaultPersistence.getDecryptedVault(sessionPasswordKey, password);
1293
+ assertCurrent();
1302
1294
  authenticated = true;
1303
1295
  const isValidSecret = (value) => {
1304
1296
  if (typeof value !== 'string' || !value)
@@ -1338,13 +1330,16 @@ class KeyringCore {
1338
1330
  throw new Error('Legacy vault secret could not be recovered. Existing vault preserved.');
1339
1331
  }
1340
1332
  // Do not change either stored record until a valid secret is recovered.
1341
- await this.vaultPersistence.setEncryptedVault({ mnemonic: decryptedMnemonic }, sessionPasswordKey);
1333
+ await this.vaultPersistence.setEncryptedVault({ mnemonic: decryptedMnemonic }, sessionPasswordKey, undefined, assertCurrent);
1334
+ assertCurrent();
1342
1335
  console.log('[KeyringManager] Vault mnemonic format normalized');
1343
1336
  // Remove currentSessionSalt from vault-keys (v4 keeps only the salt on disk)
1344
1337
  const migratedVaultKeys = {
1345
1338
  salt: vaultKeys.salt,
1346
1339
  };
1340
+ assertCurrent();
1347
1341
  await this.storage.set('vault-keys', migratedVaultKeys);
1342
+ assertCurrent();
1348
1343
  console.log('[KeyringManager] Old vault format migration completed');
1349
1344
  }
1350
1345
  // v4 vault is encrypted with the derived session password key (PBKDF2 output).
@@ -1355,6 +1350,25 @@ class KeyringCore {
1355
1350
  decryptedVault = await this.vaultPersistence.getDecryptedVault(sessionPasswordKey);
1356
1351
  }
1357
1352
  catch (error) {
1353
+ const matchesRefusedVault = accountingSalt === undefined
1354
+ ? undefined
1355
+ : current_vault_refusal_1.currentVaultRefusals.get(error);
1356
+ if (matchesRefusedVault && readVaultPair) {
1357
+ const pair = await readCurrentVaultPair(readVaultPair.bind(this.storage)).catch((readError) => {
1358
+ // A storage rejection cannot attest to password authentication.
1359
+ if (readError instanceof storage_core_1.VaultAuthenticationError)
1360
+ throw new Error('Wallet unlock vault read failed');
1361
+ throw readError;
1362
+ });
1363
+ if (!pair ||
1364
+ !matchesRefusedVault(pair.vault) ||
1365
+ pair.salt !== accountingSalt)
1366
+ throw new Error('Wallet unlock vault changed');
1367
+ // This exact negative result has no session authority. Keep it
1368
+ // available for the host counter even if its owner just retired.
1369
+ return { canLogin: false };
1370
+ }
1371
+ assertCurrent();
1358
1372
  // Only the historical CBC fallback used the 20k key without metadata.
1359
1373
  // Never try it for GCM, malformed records, or platform/storage failures.
1360
1374
  const rawVault = error instanceof storage_core_1.VaultAuthenticationError &&
@@ -1362,65 +1376,54 @@ class KeyringCore {
1362
1376
  !vaultKeys.currentSessionSalt
1363
1377
  ? await this.storage.get('vault')
1364
1378
  : undefined;
1379
+ assertCurrent();
1365
1380
  if (typeof rawVault !== 'string' || rawVault.trim().startsWith('{')) {
1366
1381
  throw error;
1367
1382
  }
1368
1383
  const legacyKey = await this.deriveLegacySessionKey(password, salt);
1384
+ assertCurrent();
1369
1385
  decryptedVault = await this.vaultPersistence.getDecryptedVault(legacyKey);
1386
+ assertCurrent();
1370
1387
  authenticated = true;
1371
1388
  sessionPasswordKey = legacyKey;
1372
1389
  // Keep the historical account wrapping key. Persist its profile before
1373
1390
  // GCM rewrapping so either interrupted write remains recoverable.
1374
- await this.storage.set('vault-keys', {
1391
+ const migratedVaultKeys = {
1375
1392
  ...vaultKeys,
1376
1393
  keyDerivation: LEGACY_ENCRYPTION_KDF,
1377
- });
1394
+ };
1395
+ assertCurrent();
1396
+ await this.storage.set('vault-keys', migratedVaultKeys);
1397
+ assertCurrent();
1378
1398
  legacyProfile = true;
1379
1399
  }
1400
+ assertCurrent();
1380
1401
  authenticated = true;
1381
1402
  if (legacyProfile && this.hasWebCrypto()) {
1382
1403
  const storedVault = await this.storage.get('vault');
1404
+ assertCurrent();
1383
1405
  if (typeof storedVault === 'string' &&
1384
1406
  !storedVault.trim().startsWith('{')) {
1385
1407
  // A failed earlier rewrap can leave the authenticated profile plus
1386
1408
  // CBC. Retry that write without changing the account wrapping key.
1387
- await this.vaultPersistence.setEncryptedVault(decryptedVault, sessionPasswordKey);
1409
+ await this.vaultPersistence.setEncryptedVault(decryptedVault, sessionPasswordKey, undefined, assertCurrent);
1410
+ assertCurrent();
1388
1411
  }
1389
1412
  }
1390
1413
  // If session data missing or corrupted, recreate from vault
1391
1414
  if (!this.sessionMnemonic) {
1392
- await this.recreateSessionFromVault(sessionPasswordKey);
1393
- }
1394
- // Re-initialize syscoin transaction handler if it was cleared on lock.
1395
- // `lockWallet()` intentionally replaces it with a plain object to drop signer references,
1396
- // so we must restore the real class instance on unlock.
1397
- if (!this.syscoinTransaction ||
1398
- typeof this.syscoinTransaction.getRecommendedFee !== 'function') {
1399
- this.syscoinTransaction = this.createSyscoinTransactions();
1400
- }
1401
- // NOTE: Active account management is now handled by vault state/Redux
1402
- // No need to explicitly set active account after unlock - it's managed externally
1403
- const vault = this.getVault();
1404
- if (vault.activeAccount?.id !== undefined && vault.activeAccount?.type) {
1405
- // Check if the active account actually exists in the accounts map
1406
- const accountType = vault.activeAccount.type;
1407
- const accountId = vault.activeAccount.id;
1408
- const accountExists = vault.accounts?.[accountType]?.[accountId];
1409
- if (!accountExists) {
1410
- console.log(`[KeyringManager] Active account ${accountType}:${accountId} not found in accounts map. This may indicate a migration from old vault format.`);
1411
- // Signal that accounts need to be created after migration
1412
- return {
1413
- canLogin: true,
1414
- needsAccountCreation: true,
1415
- };
1416
- }
1417
- console.log(`[KeyringManager] Active account ${vault.activeAccount.id} available after unlock`);
1415
+ assertCurrent = await __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_recreateSessionFromVault).call(this, sessionPasswordKey, assertCurrent);
1416
+ assertCurrent();
1417
+ console.log('[KeyringManager] Session data recreated from vault');
1418
1418
  }
1419
- return {
1420
- canLogin: true,
1421
- };
1419
+ assertCurrent();
1420
+ const result = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_finishUnlock).call(this);
1421
+ assertCurrent();
1422
+ return result;
1422
1423
  }
1423
1424
  catch (error) {
1425
+ // A superseded attempt must not report authentication or clear its winner.
1426
+ assertCurrent();
1424
1427
  if (!authenticated && error instanceof storage_core_1.VaultAuthenticationError) {
1425
1428
  return { canLogin: false };
1426
1429
  }
@@ -1432,6 +1435,12 @@ class KeyringCore {
1432
1435
  throw error;
1433
1436
  }
1434
1437
  }
1438
+ assertUtxoDerivationAvailable(account, field) {
1439
+ if (account.isImported && account.xpub === account.address)
1440
+ throw new Error(`${field} not available for single-address imported accounts`);
1441
+ if (this.isWatchOnlyAccount(account))
1442
+ throw new Error(`${field} not available for watch-only accounts`);
1443
+ }
1435
1444
  isWatchOnlyAccount(a) {
1436
1445
  if (a.isLedgerWallet || a.isTrezorWallet)
1437
1446
  return false;
@@ -1542,6 +1551,17 @@ class KeyringCore {
1542
1551
  throw error;
1543
1552
  }
1544
1553
  }
1554
+ loadBip84AddressAccount(xpub, network) {
1555
+ const { types, networks } = (0, sysweb3_network_1.getNetworkConfig)(network.slip44, network.currency);
1556
+ if (!types?.zPubType)
1557
+ throw new Error('Missing zPubType in network configuration');
1558
+ return new BIP84.fromZPub(xpub, types.zPubType, networks);
1559
+ }
1560
+ deriveFirstUtxoAddress(xpub, network) {
1561
+ if (this.isDescriptor(xpub))
1562
+ return (0, watch_descriptor_1.deriveDescriptorAddress)(xpub, network);
1563
+ return this.loadBip84AddressAccount(xpub, network).getAddress(0, false, 84);
1564
+ }
1545
1565
  async importAccount(privKey, label, options) {
1546
1566
  // Check if wallet is unlocked
1547
1567
  if (!this.isUnlocked()) {
@@ -1579,9 +1599,7 @@ class KeyringCore {
1579
1599
  ? types.zPubType.testnet.vpub
1580
1600
  : types.zPubType.mainnet.zpub;
1581
1601
  const bip84Key = (0, derivation_paths_1.convertExtendedKeyVersion)(identifier, target);
1582
- addressToStore = await this.getAddress(bip84Key, false, {
1583
- forceIndex0: true,
1584
- });
1602
+ addressToStore = this.deriveFirstUtxoAddress(bip84Key, activeNetwork);
1585
1603
  }
1586
1604
  // Validate duplicates across all account buckets, including passkey smart accounts.
1587
1605
  if (this.accountAddressExists(accounts, addressToStore) ||
@@ -1882,7 +1900,7 @@ class KeyringCore {
1882
1900
  if ((0, ethers_v6_1.getAddress)(key.address) !== request.account.expectedAddress)
1883
1901
  throw new Error('Prepared EVM signer address mismatch');
1884
1902
  assertCurrent();
1885
- const signed = (0, prepared_evm_1.signPreparedEvmEnvelope)(request.transaction, key.decryptedPrivateKey);
1903
+ const { signed } = (0, prepared_evm_1.signPreparedEvmEnvelope)(request.transaction, key.decryptedPrivateKey);
1886
1904
  hostGuard();
1887
1905
  assertCurrent();
1888
1906
  return signed;
@@ -1986,7 +2004,7 @@ class KeyringCore {
1986
2004
  else {
1987
2005
  // For UTXO, use the xpub to derive the address
1988
2006
  // Always use first receive address (index 0) for imported account display
1989
- address = await this.getAddress(xpub, false, { forceIndex0: true });
2007
+ address = this.deriveFirstUtxoAddress(xpub, vault.activeNetwork);
1990
2008
  }
1991
2009
  if (this.accountAddressExists(accounts, address, isEVM))
1992
2010
  throw new Error('Account already exists on your Wallet.');
@@ -2055,7 +2073,7 @@ class KeyringCore {
2055
2073
  : types.zPubType.mainnet.zpub;
2056
2074
  xpub = (0, derivation_paths_1.convertExtendedKeyVersion)(ledgerXpub, bip84Target);
2057
2075
  // Always use first receive address (index 0) for imported account display
2058
- address = await this.getAddress(xpub, false, { forceIndex0: true });
2076
+ address = this.deriveFirstUtxoAddress(xpub, vault.activeNetwork);
2059
2077
  }
2060
2078
  if (this.accountAddressExists(accounts, address, (0, derivation_paths_1.isEvmCoin)(coin, slip44)))
2061
2079
  throw new Error('Account already exists on your Wallet.');
@@ -2169,28 +2187,6 @@ class KeyringCore {
2169
2187
  // No gaps found, return next sequential ID
2170
2188
  return existingIds.length;
2171
2189
  }
2172
- async recreateSessionFromVault(saltedHashPassword) {
2173
- try {
2174
- // v4 vault is encrypted with the derived session password key (PBKDF2 output)
2175
- // `saltedHashPassword` is the derived session password key passed by caller.
2176
- const { mnemonic } = await this.vaultPersistence.getDecryptedVault(saltedHashPassword);
2177
- if (!mnemonic) {
2178
- throw new Error('Mnemonic not found in vault');
2179
- }
2180
- // Encrypt session data with sessionPassword hash for consistency
2181
- // This allows keyring manager to decrypt with this.sessionPassword
2182
- this.privateRecordSessionRevision++;
2183
- this.sessionPassword = new SecureBuffer(saltedHashPassword);
2184
- // Encrypt the mnemonic with session password for consistency with the rest of the code
2185
- const encryptedMnemonic = crypto_js_1.default.AES.encrypt(mnemonic, saltedHashPassword).toString();
2186
- this.sessionMnemonic = new SecureBuffer(encryptedMnemonic);
2187
- console.log('[KeyringManager] Session data recreated from vault');
2188
- }
2189
- catch (error) {
2190
- console.error('ERROR recreateSessionFromVault', { error });
2191
- throw error;
2192
- }
2193
- }
2194
2190
  async _getPrivateKeyAccountInfos(privKey, label, options) {
2195
2191
  const vault = this.getVault();
2196
2192
  const { accounts } = vault;
@@ -2595,6 +2591,8 @@ class KeyringCore {
2595
2591
  * Clean up all resources
2596
2592
  */
2597
2593
  async destroy() {
2594
+ __classPrivateFieldSet(this, _KeyringCore_deviceUnlockRetired, true, "f");
2595
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").destroyed = true;
2598
2596
  await this.lockWallet();
2599
2597
  // Clear any remaining references
2600
2598
  this.ethereumTransaction = {};
@@ -2602,4 +2600,222 @@ class KeyringCore {
2602
2600
  }
2603
2601
  }
2604
2602
  exports.KeyringCore = KeyringCore;
2603
+ _KeyringCore_deviceUnlockRetired = new WeakMap(), _KeyringCore_sessionOwnership = new WeakMap(), _KeyringCore_instances = new WeakSet(), _KeyringCore_createDeviceUnlockControls = function _KeyringCore_createDeviceUnlockControls(port, readVaultPair) {
2604
+ const readPair = () => readCurrentVaultPair(readVaultPair);
2605
+ let pending = false;
2606
+ const run = async (request, assertCurrent) => {
2607
+ if (pending)
2608
+ throw new Error('Device unlock operation is busy');
2609
+ pending = true;
2610
+ const assertOwnership = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this, 'Device unlock session changed');
2611
+ const issuing = request !== null;
2612
+ const check = () => {
2613
+ const result = assertCurrent();
2614
+ if (result !== undefined) {
2615
+ // Reject async guards and consume a possible rejected promise.
2616
+ if (result &&
2617
+ (typeof result === 'object' || typeof result === 'function'))
2618
+ void Promise.resolve(result).catch(() => undefined);
2619
+ throw new TypeError('Device unlock guard must return undefined synchronously');
2620
+ }
2621
+ if (__classPrivateFieldGet(this, _KeyringCore_deviceUnlockRetired, "f") ||
2622
+ (!issuing && (this.sessionPassword || this.sessionMnemonic))) {
2623
+ throw new Error('Device unlock session changed');
2624
+ }
2625
+ assertOwnership();
2626
+ };
2627
+ let key;
2628
+ try {
2629
+ check();
2630
+ const pair = await readPair();
2631
+ check();
2632
+ if (!pair)
2633
+ return false;
2634
+ const checkPair = async (requireLiveOwner = true) => {
2635
+ const current = await readPair();
2636
+ if (requireLiveOwner)
2637
+ check();
2638
+ if (current?.pairFingerprint !== pair.pairFingerprint ||
2639
+ current.vault !== pair.vault ||
2640
+ current.salt !== pair.salt) {
2641
+ throw new Error('Device unlock vault changed');
2642
+ }
2643
+ };
2644
+ if (request) {
2645
+ key = this.hexToBytes(await this.deriveStoredSessionKey(request.password, {
2646
+ salt: pair.salt,
2647
+ }));
2648
+ }
2649
+ else {
2650
+ const credential = await port.readCredential(pair.pairFingerprint);
2651
+ key = credential?.key;
2652
+ check();
2653
+ if (credential?.pairFingerprint !== pair.pairFingerprint)
2654
+ return false;
2655
+ }
2656
+ check();
2657
+ if (!(key instanceof Uint8Array) || key.length !== 32)
2658
+ return false;
2659
+ const keyHex = Buffer.from(key.buffer, key.byteOffset, key.byteLength).toString('hex');
2660
+ let payload;
2661
+ try {
2662
+ payload = await (0, storage_core_1.decryptCurrentVault)(pair.vault, keyHex);
2663
+ }
2664
+ catch (error) {
2665
+ if (!(error instanceof storage_core_1.VaultAuthenticationError))
2666
+ throw error;
2667
+ // Preserve a definitive wrong-password result for host accounting
2668
+ // even if enrollment was cancelled. The pair must still match and
2669
+ // this branch never installs a session or writes a credential.
2670
+ await checkPair(!issuing);
2671
+ if (!issuing)
2672
+ check();
2673
+ return false;
2674
+ }
2675
+ check();
2676
+ const mnemonic = payload && typeof payload === 'object'
2677
+ ? Object.getOwnPropertyDescriptor(payload, 'mnemonic')?.value
2678
+ : undefined;
2679
+ if (typeof mnemonic !== 'string' || !this.isSeedValid(mnemonic)) {
2680
+ throw new Error('Invalid device unlock vault payload');
2681
+ }
2682
+ await checkPair();
2683
+ check();
2684
+ if (request) {
2685
+ const result = await port.writeCredential(Object.freeze({ pairFingerprint: pair.pairFingerprint, key }), check);
2686
+ if (result !== undefined)
2687
+ throw new Error('Device unlock credential write was not acknowledged');
2688
+ await checkPair();
2689
+ check();
2690
+ }
2691
+ else {
2692
+ if (__classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_finishUnlock).call(this).needsAccountCreation)
2693
+ return false;
2694
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_installVaultSession).call(this, mnemonic, keyHex, check);
2695
+ }
2696
+ return true;
2697
+ }
2698
+ finally {
2699
+ if (key instanceof Uint8Array)
2700
+ key.fill(0);
2701
+ pending = false;
2702
+ }
2703
+ };
2704
+ return Object.freeze({
2705
+ issue: (password, assertCurrent) => {
2706
+ if (typeof password !== 'string' || !password)
2707
+ return Promise.reject(new TypeError('Wallet password is required'));
2708
+ return run({ password }, assertCurrent);
2709
+ },
2710
+ resume: (assertCurrent) => run(null, assertCurrent),
2711
+ });
2712
+ }, _KeyringCore_acceptSessionFrom = function _KeyringCore_acceptSessionFrom(source) {
2713
+ if (source.authority !== this.authority ||
2714
+ source.storage !== this.storage ||
2715
+ source.vaultPersistence !== this.vaultPersistence ||
2716
+ __classPrivateFieldGet(source, _KeyringCore_sessionOwnership, "f").tracker !== __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").tracker) {
2717
+ throw new Error('Cannot transfer session between different wallet authorities');
2718
+ }
2719
+ if (!source.sessionPassword || !source.sessionMnemonic) {
2720
+ throw new Error('Session data is missing during transfer');
2721
+ }
2722
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_replaceSessionBuffers).call(this, source.sessionPassword, source.sessionMnemonic, __classPrivateFieldGet(source, _KeyringCore_sessionOwnership, "f").token);
2723
+ }, _KeyringCore_assertSessionOwner = function _KeyringCore_assertSessionOwner(sourceToken) {
2724
+ const ownership = __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f");
2725
+ if (ownership.destroyed)
2726
+ throw new Error('Wallet keyring was destroyed');
2727
+ const owner = ownership.tracker.current;
2728
+ if (sourceToken === undefined
2729
+ ? owner !== undefined && owner !== ownership.token
2730
+ : owner !== sourceToken) {
2731
+ throw new Error('Wallet session is owned by another keyring');
2732
+ }
2733
+ }, _KeyringCore_captureSessionGuard = function _KeyringCore_captureSessionGuard(message = 'Wallet unlock session changed') {
2734
+ const ownership = __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f");
2735
+ const revision = this.privateRecordSessionRevision;
2736
+ const epoch = ownership.tracker.epoch;
2737
+ return () => {
2738
+ if (this.privateRecordSessionRevision !== revision)
2739
+ throw new Error(message);
2740
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_assertSessionOwner).call(this);
2741
+ if (ownership.tracker.epoch !== epoch)
2742
+ throw new Error(message);
2743
+ };
2744
+ }, _KeyringCore_replaceSessionBuffers = function _KeyringCore_replaceSessionBuffers(sessionPassword, sessionMnemonic, sourceToken) {
2745
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_assertSessionOwner).call(this, sourceToken);
2746
+ this.privateRecordSessionRevision++;
2747
+ // Clear any existing data first
2748
+ if (this.sessionPassword) {
2749
+ this.sessionPassword.clear();
2750
+ }
2751
+ if (this.sessionMnemonic) {
2752
+ this.sessionMnemonic.clear();
2753
+ }
2754
+ // Take ownership of the actual SecureBuffer objects
2755
+ // No copying - these are the original objects
2756
+ this.sessionPassword = sessionPassword;
2757
+ this.sessionMnemonic = sessionMnemonic;
2758
+ const ownership = __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f");
2759
+ ownership.tracker.current = ownership.token;
2760
+ ownership.tracker.epoch++;
2761
+ }, _KeyringCore_finishUnlock = function _KeyringCore_finishUnlock() {
2762
+ // Re-initialize syscoin transaction handler if it was cleared on lock.
2763
+ // `lockWallet()` intentionally replaces it with a plain object to drop signer references,
2764
+ // so we must restore the real class instance on unlock.
2765
+ if (!this.syscoinTransaction ||
2766
+ typeof this.syscoinTransaction.getRecommendedFee !== 'function') {
2767
+ this.syscoinTransaction = this.createSyscoinTransactions();
2768
+ }
2769
+ // NOTE: Active account management is now handled by vault state/Redux
2770
+ // No need to explicitly set active account after unlock - it's managed externally
2771
+ const vault = this.getVault();
2772
+ if (vault.activeAccount?.id !== undefined && vault.activeAccount?.type) {
2773
+ // Check if the active account actually exists in the accounts map
2774
+ const accountType = vault.activeAccount.type;
2775
+ const accountId = vault.activeAccount.id;
2776
+ const accountExists = vault.accounts?.[accountType]?.[accountId];
2777
+ if (!accountExists) {
2778
+ console.log(`[KeyringManager] Active account ${accountType}:${accountId} not found in accounts map. This may indicate a migration from old vault format.`);
2779
+ // Signal that accounts need to be created after migration
2780
+ return {
2781
+ canLogin: true,
2782
+ needsAccountCreation: true,
2783
+ };
2784
+ }
2785
+ console.log(`[KeyringManager] Active account ${vault.activeAccount.id} available after unlock`);
2786
+ }
2787
+ return {
2788
+ canLogin: true,
2789
+ };
2790
+ }, _KeyringCore_installVaultSession = function _KeyringCore_installVaultSession(mnemonic, key, assertCurrent) {
2791
+ const ciphertext = crypto_js_1.default.AES.encrypt(mnemonic, key).toString();
2792
+ const password = new SecureBuffer(key);
2793
+ const encryptedMnemonic = new SecureBuffer(ciphertext);
2794
+ try {
2795
+ assertCurrent?.();
2796
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_replaceSessionBuffers).call(this, password, encryptedMnemonic);
2797
+ }
2798
+ catch (error) {
2799
+ password.clear();
2800
+ encryptedMnemonic.clear();
2801
+ throw error;
2802
+ }
2803
+ }, _KeyringCore_recreateSessionFromVault = async function _KeyringCore_recreateSessionFromVault(saltedHashPassword, assertCurrent) {
2804
+ try {
2805
+ // v4 vault is encrypted with the derived session password key (PBKDF2 output)
2806
+ // `saltedHashPassword` is the derived session password key passed by caller.
2807
+ const { mnemonic } = await this.vaultPersistence.getDecryptedVault(saltedHashPassword);
2808
+ assertCurrent();
2809
+ if (!mnemonic) {
2810
+ throw new Error('Mnemonic not found in vault');
2811
+ }
2812
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_installVaultSession).call(this, mnemonic, saltedHashPassword, assertCurrent);
2813
+ // Capture the installed revision before returning to the async caller.
2814
+ return __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
2815
+ }
2816
+ catch (error) {
2817
+ console.error('ERROR recreateSessionFromVault', { error });
2818
+ throw error;
2819
+ }
2820
+ };
2605
2821
  //# sourceMappingURL=keyring-core.js.map