@bopen-io/wallet-toolbox 2.15.0-bopen.2 → 2.15.1

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 (194) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/README.md +127 -25
  3. package/docs/client.md +312 -154
  4. package/docs/monitor.md +2 -2
  5. package/docs/no-send-expiry.md +7 -2
  6. package/docs/services.md +143 -60
  7. package/docs/wallet.md +208 -117
  8. package/out/src/CWIStyleWalletManager.d.ts +36 -5
  9. package/out/src/CWIStyleWalletManager.d.ts.map +1 -1
  10. package/out/src/CWIStyleWalletManager.js +356 -60
  11. package/out/src/CWIStyleWalletManager.js.map +1 -1
  12. package/out/src/Wallet.d.ts.map +1 -1
  13. package/out/src/Wallet.js +8 -3
  14. package/out/src/Wallet.js.map +1 -1
  15. package/out/src/WalletAuthenticationManager.d.ts +3 -0
  16. package/out/src/WalletAuthenticationManager.d.ts.map +1 -1
  17. package/out/src/WalletAuthenticationManager.js +86 -3
  18. package/out/src/WalletAuthenticationManager.js.map +1 -1
  19. package/out/src/WalletPermissionsManager.d.ts +63 -9
  20. package/out/src/WalletPermissionsManager.d.ts.map +1 -1
  21. package/out/src/WalletPermissionsManager.js +213 -49
  22. package/out/src/WalletPermissionsManager.js.map +1 -1
  23. package/out/src/monitor/Monitor.js +1 -1
  24. package/out/src/monitor/Monitor.js.map +1 -1
  25. package/out/src/monitor/monitorValidation.d.ts +2 -1
  26. package/out/src/monitor/monitorValidation.d.ts.map +1 -1
  27. package/out/src/monitor/monitorValidation.js +2 -2
  28. package/out/src/monitor/monitorValidation.js.map +1 -1
  29. package/out/src/monitor/tasks/TaskNewHeader.d.ts.map +1 -1
  30. package/out/src/monitor/tasks/TaskNewHeader.js +1 -1
  31. package/out/src/monitor/tasks/TaskNewHeader.js.map +1 -1
  32. package/out/src/monitor/tasks/TaskReviewUtxos.d.ts.map +1 -1
  33. package/out/src/monitor/tasks/TaskReviewUtxos.js +2 -1
  34. package/out/src/monitor/tasks/TaskReviewUtxos.js.map +1 -1
  35. package/out/src/sdk/WERR_errors.d.ts +4 -0
  36. package/out/src/sdk/WERR_errors.d.ts.map +1 -1
  37. package/out/src/sdk/WERR_errors.js +4 -0
  38. package/out/src/sdk/WERR_errors.js.map +1 -1
  39. package/out/src/sdk/WalletErrorFromJson.d.ts.map +1 -1
  40. package/out/src/sdk/WalletErrorFromJson.js +23 -2
  41. package/out/src/sdk/WalletErrorFromJson.js.map +1 -1
  42. package/out/src/sdk/WalletStorage.interfaces.d.ts +11 -0
  43. package/out/src/sdk/WalletStorage.interfaces.d.ts.map +1 -1
  44. package/out/src/sdk/types.d.ts +12 -1
  45. package/out/src/sdk/types.d.ts.map +1 -1
  46. package/out/src/sdk/types.js +19 -2
  47. package/out/src/sdk/types.js.map +1 -1
  48. package/out/src/services/Services.d.ts.map +1 -1
  49. package/out/src/services/Services.js +5 -3
  50. package/out/src/services/Services.js.map +1 -1
  51. package/out/src/services/chaintracker/BHServiceClient.js +2 -2
  52. package/out/src/services/chaintracker/BHServiceClient.js.map +1 -1
  53. package/out/src/services/chaintracker/chaintracks/Chaintracks.js +2 -2
  54. package/out/src/services/chaintracker/chaintracks/Chaintracks.js.map +1 -1
  55. package/out/src/services/chaintracker/chaintracks/ChaintracksServiceClient.d.ts.map +1 -1
  56. package/out/src/services/chaintracker/chaintracks/ChaintracksServiceClient.js +3 -1
  57. package/out/src/services/chaintracker/chaintracks/ChaintracksServiceClient.js.map +1 -1
  58. package/out/src/services/chaintracker/chaintracks/GoChaintracksServiceClient.d.ts.map +1 -1
  59. package/out/src/services/chaintracker/chaintracks/GoChaintracksServiceClient.js +3 -1
  60. package/out/src/services/chaintracker/chaintracks/GoChaintracksServiceClient.js.map +1 -1
  61. package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorBase.js +1 -1
  62. package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorBase.js.map +1 -1
  63. package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorBase.js +1 -1
  64. package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorBase.js.map +1 -1
  65. package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorChaintracksSSE.js +1 -1
  66. package/out/src/services/chaintracker/chaintracks/Ingest/LiveIngestorChaintracksSSE.js.map +1 -1
  67. package/out/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainIngestorWs.d.ts.map +1 -1
  68. package/out/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainIngestorWs.js +2 -0
  69. package/out/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainIngestorWs.js.map +1 -1
  70. package/out/src/services/chaintracker/chaintracks/Storage/BulkStorageBase.js +2 -2
  71. package/out/src/services/chaintracker/chaintracks/Storage/BulkStorageBase.js.map +1 -1
  72. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.js +6 -6
  73. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.js.map +1 -1
  74. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageIdb.js +2 -2
  75. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageIdb.js.map +1 -1
  76. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageKnex.js +2 -2
  77. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageKnex.js.map +1 -1
  78. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageNoDb.d.ts +1 -0
  79. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageNoDb.d.ts.map +1 -1
  80. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageNoDb.js +11 -3
  81. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageNoDb.js.map +1 -1
  82. package/out/src/services/chaintracker/chaintracks/util/BulkFileDataManager.d.ts.map +1 -1
  83. package/out/src/services/chaintracker/chaintracks/util/BulkFileDataManager.js +13 -9
  84. package/out/src/services/chaintracker/chaintracks/util/BulkFileDataManager.js.map +1 -1
  85. package/out/src/services/chaintracker/chaintracks/util/BulkFilesReader.d.ts.map +1 -1
  86. package/out/src/services/chaintracker/chaintracks/util/BulkFilesReader.js +2 -2
  87. package/out/src/services/chaintracker/chaintracks/util/BulkFilesReader.js.map +1 -1
  88. package/out/src/services/chaintracker/chaintracks/util/InlineBulkFileDataValidator.d.ts.map +1 -1
  89. package/out/src/services/chaintracker/chaintracks/util/InlineBulkFileDataValidator.js +2 -2
  90. package/out/src/services/chaintracker/chaintracks/util/InlineBulkFileDataValidator.js.map +1 -1
  91. package/out/src/services/chaintracker/chaintracks/util/blockHeaderUtilities.d.ts +23 -5
  92. package/out/src/services/chaintracker/chaintracks/util/blockHeaderUtilities.d.ts.map +1 -1
  93. package/out/src/services/chaintracker/chaintracks/util/blockHeaderUtilities.js +58 -16
  94. package/out/src/services/chaintracker/chaintracks/util/blockHeaderUtilities.js.map +1 -1
  95. package/out/src/services/createDefaultWalletServicesOptions.d.ts +6 -3
  96. package/out/src/services/createDefaultWalletServicesOptions.d.ts.map +1 -1
  97. package/out/src/services/createDefaultWalletServicesOptions.js +18 -6
  98. package/out/src/services/createDefaultWalletServicesOptions.js.map +1 -1
  99. package/out/src/services/networkConfig.d.ts +7 -0
  100. package/out/src/services/networkConfig.d.ts.map +1 -1
  101. package/out/src/services/networkConfig.js +25 -2
  102. package/out/src/services/networkConfig.js.map +1 -1
  103. package/out/src/services/providers/SdkWhatsOnChain.d.ts +8 -2
  104. package/out/src/services/providers/SdkWhatsOnChain.d.ts.map +1 -1
  105. package/out/src/services/providers/SdkWhatsOnChain.js +17 -6
  106. package/out/src/services/providers/SdkWhatsOnChain.js.map +1 -1
  107. package/out/src/services/validateMerklePathResult.d.ts +4 -3
  108. package/out/src/services/validateMerklePathResult.d.ts.map +1 -1
  109. package/out/src/services/validateMerklePathResult.js +6 -6
  110. package/out/src/services/validateMerklePathResult.js.map +1 -1
  111. package/out/src/signer/actionBatch/ActionBatchWorkspace.d.ts.map +1 -1
  112. package/out/src/signer/actionBatch/ActionBatchWorkspace.js +3 -2
  113. package/out/src/signer/actionBatch/ActionBatchWorkspace.js.map +1 -1
  114. package/out/src/signer/methods/buildSignableTransaction.d.ts.map +1 -1
  115. package/out/src/signer/methods/buildSignableTransaction.js +7 -3
  116. package/out/src/signer/methods/buildSignableTransaction.js.map +1 -1
  117. package/out/src/signer/methods/createAction.d.ts +32 -1
  118. package/out/src/signer/methods/createAction.d.ts.map +1 -1
  119. package/out/src/signer/methods/createAction.js +56 -1
  120. package/out/src/signer/methods/createAction.js.map +1 -1
  121. package/out/src/signer/methods/internalizeAction.js +1 -1
  122. package/out/src/signer/methods/internalizeAction.js.map +1 -1
  123. package/out/src/signer/methods/signAction.js +8 -1
  124. package/out/src/signer/methods/signAction.js.map +1 -1
  125. package/out/src/storage/StorageIdb.js +4 -4
  126. package/out/src/storage/StorageIdb.js.map +1 -1
  127. package/out/src/storage/StorageKnex.d.ts +6 -2
  128. package/out/src/storage/StorageKnex.d.ts.map +1 -1
  129. package/out/src/storage/StorageKnex.js +114 -0
  130. package/out/src/storage/StorageKnex.js.map +1 -1
  131. package/out/src/storage/WalletStorageManager.d.ts.map +1 -1
  132. package/out/src/storage/WalletStorageManager.js +5 -0
  133. package/out/src/storage/WalletStorageManager.js.map +1 -1
  134. package/out/src/storage/methods/ListOutputsSpecOp.d.ts.map +1 -1
  135. package/out/src/storage/methods/ListOutputsSpecOp.js +84 -0
  136. package/out/src/storage/methods/ListOutputsSpecOp.js.map +1 -1
  137. package/out/src/storage/methods/attemptToPostReqsToNetwork.d.ts.map +1 -1
  138. package/out/src/storage/methods/attemptToPostReqsToNetwork.js +7 -3
  139. package/out/src/storage/methods/attemptToPostReqsToNetwork.js.map +1 -1
  140. package/out/src/storage/methods/createAction.d.ts +5 -0
  141. package/out/src/storage/methods/createAction.d.ts.map +1 -1
  142. package/out/src/storage/methods/createAction.js +13 -6
  143. package/out/src/storage/methods/createAction.js.map +1 -1
  144. package/out/src/storage/methods/generateChange.d.ts +6 -0
  145. package/out/src/storage/methods/generateChange.d.ts.map +1 -1
  146. package/out/src/storage/methods/generateChange.js +10 -3
  147. package/out/src/storage/methods/generateChange.js.map +1 -1
  148. package/out/src/storage/methods/internalizeAction.d.ts +8 -1
  149. package/out/src/storage/methods/internalizeAction.d.ts.map +1 -1
  150. package/out/src/storage/methods/internalizeAction.js +106 -42
  151. package/out/src/storage/methods/internalizeAction.js.map +1 -1
  152. package/out/src/storage/methods/listOutputsKnex.js +1 -1
  153. package/out/src/storage/methods/listOutputsKnex.js.map +1 -1
  154. package/out/src/storage/methods/noSendExpiry.d.ts.map +1 -1
  155. package/out/src/storage/methods/noSendExpiry.js +14 -4
  156. package/out/src/storage/methods/noSendExpiry.js.map +1 -1
  157. package/out/src/storage/remoting/StorageClientBase.d.ts.map +1 -1
  158. package/out/src/storage/remoting/StorageClientBase.js +15 -5
  159. package/out/src/storage/remoting/StorageClientBase.js.map +1 -1
  160. package/out/src/storage/remoting/StorageServer.d.ts.map +1 -1
  161. package/out/src/storage/remoting/StorageServer.js +12 -1
  162. package/out/src/storage/remoting/StorageServer.js.map +1 -1
  163. package/out/src/storage/remoting/syncChunkBinary.d.ts +10 -0
  164. package/out/src/storage/remoting/syncChunkBinary.d.ts.map +1 -1
  165. package/out/src/storage/remoting/syncChunkBinary.js +15 -11
  166. package/out/src/storage/remoting/syncChunkBinary.js.map +1 -1
  167. package/out/src/storage/schema/KnexMigrations.d.ts +1 -0
  168. package/out/src/storage/schema/KnexMigrations.d.ts.map +1 -1
  169. package/out/src/storage/schema/KnexMigrations.js +26 -4
  170. package/out/src/storage/schema/KnexMigrations.js.map +1 -1
  171. package/out/src/storage/sync/liveSyncGuards.d.ts +11 -0
  172. package/out/src/storage/sync/liveSyncGuards.d.ts.map +1 -0
  173. package/out/src/storage/sync/liveSyncGuards.js +22 -0
  174. package/out/src/storage/sync/liveSyncGuards.js.map +1 -0
  175. package/out/src/utility/actionOutputAuthorizations.d.ts +10 -0
  176. package/out/src/utility/actionOutputAuthorizations.d.ts.map +1 -0
  177. package/out/src/utility/actionOutputAuthorizations.js +25 -0
  178. package/out/src/utility/actionOutputAuthorizations.js.map +1 -0
  179. package/out/src/utility/index.all.d.ts +1 -0
  180. package/out/src/utility/index.all.d.ts.map +1 -1
  181. package/out/src/utility/index.all.js +1 -0
  182. package/out/src/utility/index.all.js.map +1 -1
  183. package/out/src/utility/index.client.d.ts +1 -0
  184. package/out/src/utility/index.client.d.ts.map +1 -1
  185. package/out/src/utility/index.client.js +1 -0
  186. package/out/src/utility/index.client.js.map +1 -1
  187. package/out/src/utility/numericTotalOutputs.d.ts +7 -0
  188. package/out/src/utility/numericTotalOutputs.d.ts.map +1 -0
  189. package/out/src/utility/numericTotalOutputs.js +15 -0
  190. package/out/src/utility/numericTotalOutputs.js.map +1 -0
  191. package/out/src/utility/utilityHelpers.d.ts.map +1 -1
  192. package/out/src/utility/utilityHelpers.js +2 -0
  193. package/out/src/utility/utilityHelpers.js.map +1 -1
  194. package/package.json +43 -46
@@ -24,6 +24,18 @@ exports.ARGON2ID_MAX_PARALLELISM = 16;
24
24
  exports.KDF_MAX_HASH_LENGTH = 64;
25
25
  exports.PBKDF2_MAX_ITERATIONS = 10_000_000;
26
26
  exports.MAX_STATE_SNAPSHOT_BYTES = 16 * 1024 * 1024;
27
+ const MAX_PENDING_REGISTRATION_TOKEN_BYTES = 64 * 1024;
28
+ function readSerializedOutpoint(reader, allowUnpublished) {
29
+ const outpointLen = reader.readVarIntNumStrict(false);
30
+ const currentOutpoint = (0, utils_1.toUTF8)(reader.read(outpointLen));
31
+ if ((currentOutpoint.length === 0 && !allowUnpublished) ||
32
+ currentOutpoint.length > 128 ||
33
+ (currentOutpoint.length > 0 && !/^[^\s.:]+[.:]\d+$/.test(currentOutpoint)) ||
34
+ (allowUnpublished && !reader.eof())) {
35
+ throw new Error('Serialized UMP token contains an invalid outpoint.');
36
+ }
37
+ return currentOutpoint;
38
+ }
27
39
  function isPositiveIntegerInRange(value, maximum) {
28
40
  return typeof value === 'number' && Number.isInteger(value) && value > 0 && value <= maximum;
29
41
  }
@@ -355,14 +367,15 @@ class OverlayUMPTokenInteractor {
355
367
  // spent by another candidate's transaction history. Only unrelated (forked)
356
368
  // tokens remain indeterminate.
357
369
  if (matchingTokens.length > 1) {
358
- const newest = this.resolveNewestToken(matchingTokens, resolution.answer.outputs);
370
+ const pinnedLineage = await this.findPinnedLineage(matchingTokens, resolution.answer.outputs, options?.pinnedOutpoint);
371
+ const newest = this.resolveNewestToken(pinnedLineage ?? matchingTokens, resolution.answer.outputs);
359
372
  if (newest != null) {
360
373
  this.lookupDone(lookupKind, 'found', diagnostics, startedAt, {
361
374
  supersededTokens: matchingTokens.length - 1
362
375
  });
363
376
  return newest;
364
377
  }
365
- const pinned = options?.pinnedOutpoint
378
+ const pinned = pinnedLineage == null && options?.pinnedOutpoint
366
379
  ? matchingTokens.find(token => token.currentOutpoint === options.pinnedOutpoint)
367
380
  : undefined;
368
381
  if (pinned != null) {
@@ -388,6 +401,162 @@ class OverlayUMPTokenInteractor {
388
401
  this.lookupFailed(lookupKind, reason, diagnostics, startedAt);
389
402
  throw new UMPTokenLookupError(reason, diagnostics);
390
403
  }
404
+ /** Keep a verified pin and its proven descendants together, excluding unrelated forks. */
405
+ async findPinnedLineage(tokens, outputs, pinnedOutpoint) {
406
+ if (pinnedOutpoint == null)
407
+ return undefined;
408
+ const candidates = new Map(tokens.map(token => [token.currentOutpoint, token]));
409
+ const pinSource = candidates.has(pinnedOutpoint) ? this.findLookupTransaction(outputs, pinnedOutpoint) : undefined;
410
+ const relationships = await Promise.all(outputs.map(output => this.pinRelationship(output, candidates, pinnedOutpoint, pinSource)));
411
+ const related = new Set();
412
+ let anchorSeen = false;
413
+ for (const relationship of relationships) {
414
+ if (relationship == null)
415
+ continue;
416
+ anchorSeen = true;
417
+ if (relationship.selected)
418
+ related.add(relationship.outpoint);
419
+ }
420
+ return anchorSeen ? tokens.filter(token => related.has(token.currentOutpoint)) : undefined;
421
+ }
422
+ async pinRelationship(output, candidates, pin, pinSource) {
423
+ try {
424
+ const tx = this.readTokenHistory(output.beef);
425
+ const outpoint = `${tx.id('hex')}.${output.outputIndex}`;
426
+ if (!candidates.has(outpoint))
427
+ return undefined;
428
+ if (outpoint === pin)
429
+ return { outpoint, selected: true };
430
+ const descent = this.descendsFromPin(tx, pin, pinSource);
431
+ if (descent == null)
432
+ return undefined;
433
+ return { outpoint, selected: descent === 'verified' && (await this.hasCompleteUpdateEvidence(tx)) };
434
+ }
435
+ catch {
436
+ // A malformed copy cannot establish an anchor or hide a deeper copy.
437
+ return undefined;
438
+ }
439
+ }
440
+ /** Preserve unmined funding-ancestry checks without adding a network dependency. */
441
+ async hasCompleteUpdateEvidence(tx) {
442
+ try {
443
+ return await tx.verify('scripts only');
444
+ }
445
+ catch {
446
+ // An input reference alone does not establish a usable token update.
447
+ return false;
448
+ }
449
+ }
450
+ descendsFromPin(tx, pin, pinSource) {
451
+ const pending = [tx];
452
+ const visited = new Set();
453
+ let incomplete = false;
454
+ while (pending.length > 0) {
455
+ const current = pending.pop();
456
+ const txid = current.id('hex');
457
+ if (visited.has(txid))
458
+ continue;
459
+ visited.add(txid);
460
+ for (const [inputIndex] of current.inputs.entries()) {
461
+ const step = this.pinInputDescent(current, inputIndex, pin, pinSource);
462
+ if (step.descent === 'verified')
463
+ return step.descent;
464
+ incomplete = incomplete || step.descent === 'incomplete';
465
+ if (step.predecessor != null)
466
+ pending.push(step.predecessor);
467
+ }
468
+ }
469
+ return incomplete ? 'incomplete' : undefined;
470
+ }
471
+ pinInputDescent(tx, inputIndex, pin, pinSource) {
472
+ const input = tx.inputs[inputIndex];
473
+ const sourceTxid = input.sourceTXID ?? input.sourceTransaction?.id('hex');
474
+ const isPin = sourceTxid != null && `${sourceTxid}.${input.sourceOutputIndex}` === pin;
475
+ const source = input.sourceTransaction ?? (isPin ? pinSource : undefined);
476
+ const refused = { descent: isPin ? 'incomplete' : undefined };
477
+ if (source == null || !this.isAuthenticatedUMPOutput(source.outputs[input.sourceOutputIndex]))
478
+ return refused;
479
+ if (!this.verifyUMPSpend(tx, inputIndex, source))
480
+ return refused;
481
+ return isPin ? { descent: 'verified' } : { predecessor: source };
482
+ }
483
+ isAuthenticatedUMPOutput(output) {
484
+ if (output?.satoshis !== 1)
485
+ return false;
486
+ try {
487
+ const { fields } = decodeAuthenticatedUMPFields(output.lockingScript);
488
+ return (fields.length >= 11 &&
489
+ fields[6]?.length === 32 &&
490
+ fields[7]?.length === 32 &&
491
+ fields.slice(0, 11).every(field => field.length > 0 && field.length <= exports.MAX_STATE_SNAPSHOT_BYTES));
492
+ }
493
+ catch {
494
+ // A funding input or unauthenticated predecessor is not a UMP anchor.
495
+ return false;
496
+ }
497
+ }
498
+ /** Check token-control continuity at every edge, including confirmed transactions. */
499
+ verifyUMPSpend(tx, inputIndex, source) {
500
+ try {
501
+ const input = tx.inputs[inputIndex];
502
+ const sourceTxid = source.id('hex');
503
+ if (input.unlockingScript == null || (input.sourceTXID != null && input.sourceTXID !== sourceTxid))
504
+ return false;
505
+ const output = source.outputs[input.sourceOutputIndex];
506
+ return new sdk_1.Spend({
507
+ sourceTXID: sourceTxid,
508
+ sourceOutputIndex: input.sourceOutputIndex,
509
+ sourceSatoshis: output.satoshis ?? 0,
510
+ lockingScript: output.lockingScript,
511
+ transactionVersion: tx.version,
512
+ otherInputs: tx.inputs.filter((_, index) => index !== inputIndex),
513
+ unlockingScript: input.unlockingScript,
514
+ inputSequence: input.sequence ?? 0xffffffff,
515
+ inputIndex,
516
+ outputs: tx.outputs,
517
+ lockTime: tx.lockTime
518
+ }).validate();
519
+ }
520
+ catch {
521
+ return false;
522
+ }
523
+ }
524
+ findLookupTransaction(outputs, outpoint) {
525
+ for (const output of outputs) {
526
+ try {
527
+ const tx = this.readTokenHistory(output.beef);
528
+ if (`${tx.id('hex')}.${output.outputIndex}` === outpoint)
529
+ return tx;
530
+ }
531
+ catch {
532
+ // A malformed copy does not hide another host's usable anchor.
533
+ }
534
+ }
535
+ return undefined;
536
+ }
537
+ /** Link explicit overlay history even when an ancestor already has a Merkle proof. */
538
+ readTokenHistory(bytes) {
539
+ const tx = sdk_1.Transaction.fromBEEF(bytes);
540
+ const beef = sdk_1.Beef.fromBinary(bytes);
541
+ const pending = [tx];
542
+ const visited = new Set();
543
+ while (pending.length > 0) {
544
+ const current = pending.pop();
545
+ const txid = current.id('hex');
546
+ if (visited.has(txid))
547
+ continue;
548
+ visited.add(txid);
549
+ for (const input of current.inputs) {
550
+ const sourceTxid = input.sourceTXID ?? input.sourceTransaction?.id('hex');
551
+ if (input.sourceTransaction == null && sourceTxid != null) {
552
+ input.sourceTransaction = beef.findAtomicTransaction(sourceTxid);
553
+ }
554
+ if (input.sourceTransaction != null)
555
+ pending.push(input.sourceTransaction);
556
+ }
557
+ }
558
+ return tx;
559
+ }
391
560
  /**
392
561
  * Picks the newest rendition among distinct verified tokens, when possible.
393
562
  *
@@ -413,7 +582,7 @@ class OverlayUMPTokenInteractor {
413
582
  const evidenceByCandidate = new Map();
414
583
  for (const output of outputs) {
415
584
  try {
416
- const tx = sdk_1.Transaction.fromBEEF(output.beef);
585
+ const tx = this.readTokenHistory(output.beef);
417
586
  const outpoint = `${tx.id('hex')}.${output.outputIndex}`;
418
587
  if (!candidates.has(outpoint))
419
588
  continue;
@@ -916,6 +1085,8 @@ class CWIStyleWalletManager {
916
1085
  * Optional function to fund a new Wallet after the new-user flow.
917
1086
  */
918
1087
  newWalletFunder;
1088
+ /** Persists only encrypted UMP fields before an irreversible funding attempt. */
1089
+ pendingRegistrationTokenSaver;
919
1090
  /**
920
1091
  * Builds the underlying wallet for a specific profile.
921
1092
  */
@@ -932,6 +1103,8 @@ class CWIStyleWalletManager {
932
1103
  * The current UMP token in use.
933
1104
  */
934
1105
  currentUMPToken;
1106
+ /** Unpublished token retained through an interrupted new-user registration. */
1107
+ pendingRegistrationToken;
935
1108
  /**
936
1109
  * Temporarily retained presentation key.
937
1110
  */
@@ -952,6 +1125,7 @@ class CWIStyleWalletManager {
952
1125
  * List of loaded non-default profiles.
953
1126
  */
954
1127
  profiles = [];
1128
+ pendingProfileNames = new Set();
955
1129
  /**
956
1130
  * The underlying wallet instance for the *active* profile.
957
1131
  */
@@ -975,8 +1149,9 @@ class CWIStyleWalletManager {
975
1149
  * @param newWalletFunder Optional function to fund a new wallet.
976
1150
  * @param stateSnapshot Optional previously saved state snapshot.
977
1151
  * @param kdfConfig Optional KDF configuration for new UMP tokens.
1152
+ * @param pendingRegistrationTokenSaver Optional durable store for an unpublished UMP token.
978
1153
  */
979
- constructor(...[adminOriginator, walletBuilder, interactor, recoveryKeySaver, passwordRetriever, newWalletFunder, stateSnapshot, kdfConfig, telemetry]) {
1154
+ constructor(...[adminOriginator, walletBuilder, interactor, recoveryKeySaver, passwordRetriever, newWalletFunder, stateSnapshot, kdfConfig, telemetry, pendingRegistrationTokenSaver]) {
980
1155
  this.adminOriginator = adminOriginator;
981
1156
  this.walletBuilder = walletBuilder;
982
1157
  this.telemetry = new sdk_1.Telemetry(telemetry);
@@ -985,6 +1160,7 @@ class CWIStyleWalletManager {
985
1160
  this.passwordRetriever = passwordRetriever;
986
1161
  this.authenticated = false;
987
1162
  this.newWalletFunder = newWalletFunder;
1163
+ this.pendingRegistrationTokenSaver = pendingRegistrationTokenSaver;
988
1164
  // Initialize KDF config with Argon2id defaults for v3 tokens
989
1165
  const kdfAlgorithm = kdfConfig?.algorithm ?? 'argon2id';
990
1166
  this.kdfConfig = {
@@ -1027,6 +1203,40 @@ class CWIStyleWalletManager {
1027
1203
  }
1028
1204
  }
1029
1205
  // --- Authentication Methods ---
1206
+ /**
1207
+ * Restores an unpublished UMP token before authentication. The token contains
1208
+ * an encrypted wallet root, but no plaintext root, snapshot key, or on-chain outpoint.
1209
+ */
1210
+ loadPendingRegistrationToken(bytes) {
1211
+ if (this.authenticated || this.authenticationFlow !== 'unknown' || this.currentUMPToken != null) {
1212
+ throw new Error('Pending registration token must be loaded before authentication.');
1213
+ }
1214
+ if (!Array.isArray(bytes) ||
1215
+ bytes.length === 0 ||
1216
+ bytes.length > MAX_PENDING_REGISTRATION_TOKEN_BYTES ||
1217
+ bytes.some(byte => !Number.isInteger(byte) || byte < 0 || byte > 255)) {
1218
+ throw new Error('Pending registration token is invalid.');
1219
+ }
1220
+ try {
1221
+ const token = this.deserializeUMPToken(bytes, true);
1222
+ if (token.currentOutpoint != null ||
1223
+ token.umpVersion !== 3 ||
1224
+ !isValidKdfConfig(token.passwordKdf) ||
1225
+ token.profilesEncrypted != null ||
1226
+ token.passwordSalt.length !== 32 ||
1227
+ token.presentationHash.length !== 32 ||
1228
+ token.recoveryHash.length !== 32) {
1229
+ throw new Error('Invalid unpublished token fields.');
1230
+ }
1231
+ this.pendingRegistrationToken = token;
1232
+ }
1233
+ catch {
1234
+ throw new Error('Pending registration token is invalid.');
1235
+ }
1236
+ }
1237
+ hasPendingRegistrationToken() {
1238
+ return this.pendingRegistrationToken != null;
1239
+ }
1030
1240
  /**
1031
1241
  * Provides the presentation key. A WAB operator pin may be supplied by the
1032
1242
  * authentication manager; normal lookup and lineage resolution always run
@@ -1044,6 +1254,7 @@ class CWIStyleWalletManager {
1044
1254
  }
1045
1255
  this.authenticationFlow = 'unknown';
1046
1256
  const hash = (0, Hash_1.sha256)(key);
1257
+ const pendingTokenMatches = this.pendingRegistrationToken == null || (0, utils_1.toHex)(this.pendingRegistrationToken.presentationHash) === (0, utils_1.toHex)(hash);
1047
1258
  const startedAt = Date.now();
1048
1259
  this.telemetry.capture({
1049
1260
  name: 'wallet-toolbox.authentication.account-lookup.started',
@@ -1070,14 +1281,19 @@ class CWIStyleWalletManager {
1070
1281
  }
1071
1282
  if (token == null) {
1072
1283
  // No token found -> New user
1284
+ if (!pendingTokenMatches) {
1285
+ throw new Error('Pending registration token belongs to another presentation key.');
1286
+ }
1073
1287
  this.authenticationFlow = 'new-user';
1074
1288
  this.presentationKey = key;
1289
+ this.currentUMPToken = this.pendingRegistrationToken;
1075
1290
  }
1076
1291
  else {
1077
1292
  // Found token -> existing user
1078
1293
  this.authenticationFlow = 'existing-user';
1079
1294
  this.presentationKey = key;
1080
1295
  this.currentUMPToken = token;
1296
+ this.clearPublishedPendingRegistration(token);
1081
1297
  }
1082
1298
  this.telemetry.capture({
1083
1299
  name: 'wallet-toolbox.authentication.account-lookup.completed',
@@ -1139,6 +1355,62 @@ class CWIStyleWalletManager {
1139
1355
  }
1140
1356
  if (this.presentationKey == null)
1141
1357
  throw new Error('No presentation key provided for new-user flow.');
1358
+ let newToken;
1359
+ let rootPrimaryKey;
1360
+ if (this.pendingRegistrationToken != null) {
1361
+ newToken = this.pendingRegistrationToken;
1362
+ rootPrimaryKey = await this.unlockPendingRegistrationToken(newToken, this.presentationKey, password);
1363
+ }
1364
+ else {
1365
+ const generated = await this.buildPendingRegistrationToken(password, this.presentationKey);
1366
+ newToken = generated.token;
1367
+ rootPrimaryKey = generated.rootPrimaryKey;
1368
+ this.pendingRegistrationToken = newToken;
1369
+ }
1370
+ this.currentUMPToken = newToken;
1371
+ // The caller must durably store the same encrypted token before a faucet
1372
+ // output can be redeemed. Repeating the write also repairs a prior failed
1373
+ // persistence attempt before any retry reaches the funder.
1374
+ if (this.pendingRegistrationTokenSaver != null) {
1375
+ await this.pendingRegistrationTokenSaver(this.serializeUMPToken(newToken, true));
1376
+ }
1377
+ await this.setupRoot(rootPrimaryKey, undefined, false);
1378
+ await this.activateProfile(exports.DEFAULT_PROFILE_ID);
1379
+ // Fund the *default* wallet if funder provided
1380
+ if (this.newWalletFunder != null && this.underlying != null) {
1381
+ try {
1382
+ await this.newWalletFunder(this.presentationKey, this.underlying, this.adminOriginator);
1383
+ }
1384
+ catch (error) {
1385
+ this.telemetry.capture({
1386
+ name: 'wallet-toolbox.authentication.new-wallet-funding.failed',
1387
+ component: CWI_COMPONENT,
1388
+ severity: 'error',
1389
+ error: new Error('New wallet funding failed.')
1390
+ });
1391
+ const message = error instanceof Error ? error.message : String(error);
1392
+ throw new Error(`Failed to fund new wallet before publishing UMP token: ${message}`, { cause: error });
1393
+ }
1394
+ }
1395
+ if (this.underlying == null)
1396
+ throw new Error('Default profile wallet not built before attempting to publish UMP token.');
1397
+ newToken.currentOutpoint = await this.UMPTokenInteractor.buildAndSend(this.underlying, this.adminOriginator, newToken);
1398
+ this.pendingRegistrationToken = undefined;
1399
+ this.authenticated = true;
1400
+ }
1401
+ async unlockPendingRegistrationToken(token, presentationKey, password) {
1402
+ try {
1403
+ const passwordKey = await derivePasswordKey(token, (0, utils_1.toArray)(password, 'utf8'));
1404
+ const rootPrimaryKey = new sdk_1.SymmetricKey(this.XOR(presentationKey, passwordKey)).decrypt(token.passwordPresentationPrimary);
1405
+ if (rootPrimaryKey.length !== 32)
1406
+ throw new Error('Invalid root key length.');
1407
+ return rootPrimaryKey;
1408
+ }
1409
+ catch {
1410
+ throw new Error('Pending registration token could not be unlocked with this password.');
1411
+ }
1412
+ }
1413
+ async buildPendingRegistrationToken(password, presentationKey) {
1142
1414
  // Generate new keys/salt
1143
1415
  const recoveryKey = (0, sdk_1.Random)(32);
1144
1416
  await this.recoveryKeySaver(recoveryKey);
@@ -1147,52 +1419,31 @@ class CWIStyleWalletManager {
1147
1419
  const rootPrimaryKey = (0, sdk_1.Random)(32);
1148
1420
  const rootPrivilegedKey = (0, sdk_1.Random)(32);
1149
1421
  // Build XOR-combined symmetric keys
1150
- const presentationPassword = new sdk_1.SymmetricKey(this.XOR(this.presentationKey, passwordKey));
1151
- const presentationRecovery = new sdk_1.SymmetricKey(this.XOR(this.presentationKey, recoveryKey));
1422
+ const presentationPassword = new sdk_1.SymmetricKey(this.XOR(presentationKey, passwordKey));
1423
+ const presentationRecovery = new sdk_1.SymmetricKey(this.XOR(presentationKey, recoveryKey));
1152
1424
  const recoveryPassword = new sdk_1.SymmetricKey(this.XOR(recoveryKey, passwordKey));
1153
1425
  const primaryPassword = new sdk_1.SymmetricKey(this.XOR(rootPrimaryKey, passwordKey));
1154
1426
  const tempPrivilegedKeyManager = new PrivilegedKeyManager_1.PrivilegedKeyManager(async () => new sdk_1.PrivateKey(rootPrivilegedKey));
1155
1427
  const wrapKey = async (plaintext) => (await tempPrivilegedKeyManager.encrypt({ plaintext, protocolID: [2, 'admin key wrapping'], keyID: '1' }))
1156
1428
  .ciphertext;
1157
1429
  // Build new UMP token (v3 with KDF metadata, no profiles initially)
1158
- const newToken = {
1430
+ const token = {
1159
1431
  passwordSalt,
1160
1432
  passwordPresentationPrimary: presentationPassword.encrypt(rootPrimaryKey),
1161
1433
  passwordRecoveryPrimary: recoveryPassword.encrypt(rootPrimaryKey),
1162
1434
  presentationRecoveryPrimary: presentationRecovery.encrypt(rootPrimaryKey),
1163
1435
  passwordPrimaryPrivileged: primaryPassword.encrypt(rootPrivilegedKey),
1164
1436
  presentationRecoveryPrivileged: presentationRecovery.encrypt(rootPrivilegedKey),
1165
- presentationHash: (0, Hash_1.sha256)(this.presentationKey),
1437
+ presentationHash: (0, Hash_1.sha256)(presentationKey),
1166
1438
  recoveryHash: (0, Hash_1.sha256)(recoveryKey),
1167
- presentationKeyEncrypted: await wrapKey(this.presentationKey),
1439
+ presentationKeyEncrypted: await wrapKey(presentationKey),
1168
1440
  passwordKeyEncrypted: await wrapKey(passwordKey),
1169
1441
  recoveryKeyEncrypted: await wrapKey(recoveryKey),
1170
1442
  profilesEncrypted: undefined,
1171
1443
  umpVersion: 3,
1172
1444
  passwordKdf: this.kdfConfig
1173
1445
  };
1174
- this.currentUMPToken = newToken;
1175
- await this.setupRoot(rootPrimaryKey);
1176
- await this.switchProfile(exports.DEFAULT_PROFILE_ID);
1177
- // Fund the *default* wallet if funder provided
1178
- if (this.newWalletFunder != null && this.underlying != null) {
1179
- try {
1180
- await this.newWalletFunder(this.presentationKey, this.underlying, this.adminOriginator);
1181
- }
1182
- catch (error) {
1183
- this.telemetry.capture({
1184
- name: 'wallet-toolbox.authentication.new-wallet-funding.failed',
1185
- component: CWI_COMPONENT,
1186
- severity: 'error',
1187
- error: new Error('New wallet funding failed.')
1188
- });
1189
- const message = error instanceof Error ? error.message : String(error);
1190
- throw new Error(`Failed to fund new wallet before publishing UMP token: ${message}`, { cause: error });
1191
- }
1192
- }
1193
- if (this.underlying == null)
1194
- throw new Error('Default profile wallet not built before attempting to publish UMP token.');
1195
- this.currentUMPToken.currentOutpoint = await this.UMPTokenInteractor.buildAndSend(this.underlying, this.adminOriginator, newToken);
1446
+ return { token, rootPrimaryKey };
1196
1447
  }
1197
1448
  /**
1198
1449
  * Provides the recovery key.
@@ -1325,6 +1576,7 @@ class CWIStyleWalletManager {
1325
1576
  // Setup root infrastructure, load profiles, and switch to the loaded active profile
1326
1577
  await this.setupRoot(rootPrimaryKey); // Will automatically load profiles
1327
1578
  await this.switchProfile(activeProfileId); // Switch to the profile saved in the snapshot
1579
+ this.clearPublishedPendingRegistration(token);
1328
1580
  this.authenticationFlow = 'existing-user'; // Loading implies existing user
1329
1581
  this.telemetry.capture({
1330
1582
  name: 'wallet-toolbox.snapshot.loaded',
@@ -1373,6 +1625,15 @@ class CWIStyleWalletManager {
1373
1625
  this.saveSnapshot();
1374
1626
  return true;
1375
1627
  }
1628
+ clearPublishedPendingRegistration(token) {
1629
+ // A conflicting wallet root can share the same presentation key; match the
1630
+ // encrypted root field before discarding the unpublished token.
1631
+ if (this.pendingRegistrationToken != null &&
1632
+ (0, utils_1.toHex)(this.pendingRegistrationToken.presentationHash) === (0, utils_1.toHex)(token.presentationHash) &&
1633
+ (0, utils_1.toHex)(this.pendingRegistrationToken.passwordPresentationPrimary) === (0, utils_1.toHex)(token.passwordPresentationPrimary)) {
1634
+ this.pendingRegistrationToken = undefined;
1635
+ }
1636
+ }
1376
1637
  /**
1377
1638
  * Destroys the wallet state, clearing keys, tokens, and profiles.
1378
1639
  */
@@ -1382,6 +1643,7 @@ class CWIStyleWalletManager {
1382
1643
  this.authenticated = false;
1383
1644
  this.rootPrimaryKey = undefined;
1384
1645
  this.currentUMPToken = undefined;
1646
+ this.pendingRegistrationToken = undefined;
1385
1647
  this.presentationKey = undefined;
1386
1648
  this.recoveryKey = undefined;
1387
1649
  this.profiles = [];
@@ -1434,7 +1696,10 @@ class CWIStyleWalletManager {
1434
1696
  throw new Error('Wallet not fully initialized or authenticated.');
1435
1697
  }
1436
1698
  // Ensure name is unique (including 'default')
1437
- if (name === 'default' || this.profiles.some(p => p.name.toLowerCase() === name.toLowerCase())) {
1699
+ const normalizedName = name.toLowerCase();
1700
+ if (name === 'default' ||
1701
+ this.pendingProfileNames.has(normalizedName) ||
1702
+ this.profiles.some(p => p.name.toLowerCase() === normalizedName)) {
1438
1703
  throw new Error(`Profile name "${name}" is already in use.`);
1439
1704
  }
1440
1705
  const newProfile = {
@@ -1444,14 +1709,26 @@ class CWIStyleWalletManager {
1444
1709
  privilegedPad: (0, sdk_1.Random)(32),
1445
1710
  createdAt: Math.floor(Date.now() / 1000)
1446
1711
  };
1447
- this.profiles.push(newProfile);
1448
- // Update the UMP token with the new profile list
1449
- await this.updateFactors(this.currentUMPToken.passwordSalt,
1450
- // Need to re-derive/decrypt factors needed for re-encryption
1451
- await this.getFactor('passwordKey'), await this.getFactor('presentationKey'), await this.getFactor('recoveryKey'), this.rootPrimaryKey, await this.getFactor('privilegedKey'), // Get ROOT privileged key
1452
- this.profiles // Pass the updated profile list
1453
- );
1454
- return newProfile.id;
1712
+ // Reserve the case-insensitive name before the first await. The profile
1713
+ // itself remains unpublished until updateFactors accepts the new token.
1714
+ this.pendingProfileNames.add(normalizedName);
1715
+ try {
1716
+ // Only keep the new profile once the UMP token holds it, so a failed update
1717
+ // leaves the list as it was and a retry is not rejected as a duplicate.
1718
+ // Update the UMP token with the new profile list
1719
+ await this.updateFactors(this.currentUMPToken.passwordSalt,
1720
+ // Need to re-derive/decrypt factors needed for re-encryption
1721
+ await this.getFactor('passwordKey'), await this.getFactor('presentationKey'), await this.getFactor('recoveryKey'), this.rootPrimaryKey, await this.getFactor('privilegedKey'), // Get ROOT privileged key
1722
+ // Built last, after the awaits above: arguments are evaluated left to
1723
+ // right, and updateFactors captures the token it consumes before its first
1724
+ // await, so this list and that token are one consistent snapshot. Built
1725
+ // earlier, a profile added by an overlapping call would be dropped.
1726
+ [...this.profiles, newProfile]);
1727
+ return newProfile.id;
1728
+ }
1729
+ finally {
1730
+ this.pendingProfileNames.delete(normalizedName);
1731
+ }
1455
1732
  }
1456
1733
  /**
1457
1734
  * Deletes a profile by its ID.
@@ -1474,16 +1751,27 @@ class CWIStyleWalletManager {
1474
1751
  if (profileIndex === -1) {
1475
1752
  throw new Error('Profile not found.');
1476
1753
  }
1477
- // Remove the profile
1478
- this.profiles.splice(profileIndex, 1);
1754
+ // Remove the profile only once the UMP token no longer holds it, so a
1755
+ // failed update leaves the profile (and the active wallet) in place.
1756
+ const wasActive = this.activeProfileId.every((x, i) => x === profileId[i]);
1479
1757
  // If the deleted profile was active, switch to default
1480
- if (this.activeProfileId.every((x, i) => x === profileId[i])) {
1758
+ if (wasActive) {
1481
1759
  await this.switchProfile(exports.DEFAULT_PROFILE_ID); // This rebuilds the wallet
1482
1760
  }
1483
1761
  // Update the UMP token
1484
- await this.updateFactors(this.currentUMPToken.passwordSalt, await this.getFactor('passwordKey'), await this.getFactor('presentationKey'), await this.getFactor('recoveryKey'), this.rootPrimaryKey, await this.getFactor('privilegedKey'), // Get ROOT privileged key
1485
- this.profiles // Pass updated list
1486
- );
1762
+ try {
1763
+ await this.updateFactors(this.currentUMPToken.passwordSalt, await this.getFactor('passwordKey'), await this.getFactor('presentationKey'), await this.getFactor('recoveryKey'), this.rootPrimaryKey, await this.getFactor('privilegedKey'), // Get ROOT privileged key
1764
+ // Built last, after the awaits, for the same reason as in addProfile.
1765
+ this.profiles.filter(p => !p.id.every((x, i) => x === profileId[i])));
1766
+ }
1767
+ catch (error) {
1768
+ // Back to the profile we left, unless the token was published before the
1769
+ // failure. A failed switch back must not hide the original error.
1770
+ if (wasActive && this.profiles.some(p => p.id.every((x, i) => x === profileId[i]))) {
1771
+ await this.switchProfile(profileId).catch(() => { });
1772
+ }
1773
+ throw error;
1774
+ }
1487
1775
  }
1488
1776
  /**
1489
1777
  * Switches the active profile. This re-derives keys and rebuilds the underlying wallet.
@@ -1494,6 +1782,13 @@ class CWIStyleWalletManager {
1494
1782
  if (!this.authenticated || this.rootPrimaryKey == null || this.rootPrivilegedKeyManager == null) {
1495
1783
  throw new Error('Cannot switch profile: Wallet not authenticated or root keys missing.');
1496
1784
  }
1785
+ await this.activateProfile(profileId);
1786
+ }
1787
+ /** Build an internal profile wallet while an unpublished registration is not yet authenticated. */
1788
+ async activateProfile(profileId) {
1789
+ if (this.rootPrimaryKey == null || this.rootPrivilegedKeyManager == null) {
1790
+ throw new Error('Cannot activate profile: root keys are missing.');
1791
+ }
1497
1792
  let profilePrimaryKey;
1498
1793
  let profilePrivilegedPad; // Pad for the target profile
1499
1794
  if (profileId.every(x => x === 0)) {
@@ -1770,7 +2065,12 @@ class CWIStyleWalletManager {
1770
2065
  );
1771
2066
  // Update the manager's state
1772
2067
  this.currentUMPToken = newTokenData;
1773
- // Profiles are already updated in this.profiles if they were passed in
2068
+ // Keep the profiles in step with the token that now holds them. Callers
2069
+ // pass a new list rather than changing this.profiles up front, so a
2070
+ // failed update leaves it untouched.
2071
+ if (profiles != null) {
2072
+ this.profiles = profiles;
2073
+ }
1774
2074
  }
1775
2075
  finally {
1776
2076
  // Switch back if we temporarily switched
@@ -1789,8 +2089,8 @@ class CWIStyleWalletManager {
1789
2089
  * Serializes a UMP token to binary format (Version 3 with KDF metadata, Version 2 with profiles).
1790
2090
  * V3 Layout: [1 byte version=3] + [11 * (varint len + bytes) for standard fields] + [1 byte profile_flag] + [IF flag=1 THEN varint len + profile bytes] + [1 byte kdf_flag] + [IF flag=1 THEN kdf metadata] + [varint len + outpoint bytes]
1791
2091
  */
1792
- serializeUMPToken(token) {
1793
- if (!token.currentOutpoint) {
2092
+ serializeUMPToken(token, allowUnpublished = false) {
2093
+ if (!token.currentOutpoint && !allowUnpublished) {
1794
2094
  throw new Error('Token must have outpoint for serialization');
1795
2095
  }
1796
2096
  const writer = new utils_1.Writer();
@@ -1847,7 +2147,7 @@ class CWIStyleWalletManager {
1847
2147
  writer.writeUInt8(0); // Flag indicating no KDF metadata
1848
2148
  }
1849
2149
  // Write outpoint string
1850
- const outpointBytes = (0, utils_1.toArray)(token.currentOutpoint, 'utf8');
2150
+ const outpointBytes = (0, utils_1.toArray)(token.currentOutpoint ?? '', 'utf8');
1851
2151
  writer.writeVarIntNum(outpointBytes.length);
1852
2152
  writer.write(outpointBytes);
1853
2153
  return writer.toArray();
@@ -1855,7 +2155,7 @@ class CWIStyleWalletManager {
1855
2155
  /**
1856
2156
  * Deserializes a UMP token from binary format (Handles Version 1, 2, and 3).
1857
2157
  */
1858
- deserializeUMPToken(bin) {
2158
+ deserializeUMPToken(bin, allowUnpublished = false) {
1859
2159
  const reader = new utils_1.Reader(bin);
1860
2160
  const version = reader.readUInt8();
1861
2161
  if (version !== 1 && version !== 2 && version !== 3) {
@@ -1913,12 +2213,7 @@ class CWIStyleWalletManager {
1913
2213
  }
1914
2214
  }
1915
2215
  // Read outpoint string
1916
- const outpointLen = reader.readVarIntNumStrict(false);
1917
- const outpointBytes = reader.read(outpointLen);
1918
- const currentOutpoint = (0, utils_1.toUTF8)(outpointBytes);
1919
- if (currentOutpoint.length > 128 || !/^[^\s.:]+[.:]\d+$/.test(currentOutpoint)) {
1920
- throw new Error('Serialized UMP token contains an invalid outpoint.');
1921
- }
2216
+ const currentOutpoint = readSerializedOutpoint(reader, allowUnpublished);
1922
2217
  const token = {
1923
2218
  passwordSalt,
1924
2219
  passwordPresentationPrimary,
@@ -1932,7 +2227,7 @@ class CWIStyleWalletManager {
1932
2227
  passwordKeyEncrypted,
1933
2228
  recoveryKeyEncrypted,
1934
2229
  profilesEncrypted,
1935
- currentOutpoint,
2230
+ ...(currentOutpoint.length > 0 ? { currentOutpoint } : {}),
1936
2231
  umpVersion,
1937
2232
  passwordKdf
1938
2233
  };
@@ -1946,7 +2241,7 @@ class CWIStyleWalletManager {
1946
2241
  * @param rootPrimaryKey The user's root primary key (32 bytes).
1947
2242
  * @param ephemeralRootPrivilegedKey Optional root privileged key (e.g., during recovery flows).
1948
2243
  */
1949
- async setupRoot(rootKey, ephemeralRootPrivilegedKey) {
2244
+ setupRoot(rootKey, ephemeralRootPrivilegedKey, authenticated = true) {
1950
2245
  if (this.currentUMPToken == null) {
1951
2246
  throw new Error('A UMP token must exist before setting up root infrastructure!');
1952
2247
  }
@@ -2006,9 +2301,10 @@ class CWIStyleWalletManager {
2006
2301
  throw new Error(`Failed to load profiles: ${message}`, { cause: error });
2007
2302
  }
2008
2303
  }
2009
- this.authenticated = true;
2304
+ this.authenticated = authenticated;
2010
2305
  // Note: We don't call switchProfile here anymore.
2011
2306
  // It's called by the auth methods (providePassword/provideRecoveryKey) or loadSnapshot after this.
2307
+ return Promise.resolve();
2012
2308
  }
2013
2309
  /*
2014
2310
  * ---------------------------------------------------------------------------------------