@spirobel/monero-wallet-api 0.5.5 → 0.6.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 (101) hide show
  1. package/dist/actionlog/actionLogOpened.d.ts +96 -1
  2. package/dist/actionlog/actionLogOpened.js +386 -1
  3. package/dist/actionlog/actionLogTypes.d.ts +114 -0
  4. package/dist/actionlog/actionLogTypes.js +198 -0
  5. package/dist/actionlog/backend/idbBackend.d.ts +13 -0
  6. package/dist/actionlog/backend/idbBackend.js +109 -0
  7. package/dist/actionlog/backend/jsonlBackend.d.ts +19 -0
  8. package/dist/actionlog/backend/jsonlBackend.js +110 -0
  9. package/dist/actionlog/backend/memoryBackend.d.ts +11 -0
  10. package/dist/actionlog/backend/memoryBackend.js +31 -0
  11. package/dist/actionlog/backend/sqliteBackend.d.ts +14 -0
  12. package/dist/actionlog/backend/sqliteBackend.js +96 -0
  13. package/dist/actionlog/backend.d.ts +12 -0
  14. package/dist/actionlog/backend.js +1 -0
  15. package/dist/actionlog/idbBackend.d.ts +19 -0
  16. package/dist/actionlog/idbBackend.js +98 -0
  17. package/dist/actionlog/jsonlBackend.d.ts +19 -0
  18. package/dist/actionlog/jsonlBackend.js +75 -0
  19. package/dist/actionlog/memoryBackend.d.ts +14 -0
  20. package/dist/actionlog/memoryBackend.js +38 -0
  21. package/dist/actionlog/openMaps.d.ts +11 -0
  22. package/dist/actionlog/openMaps.js +32 -0
  23. package/dist/actionlog/sqliteBackend.d.ts +18 -0
  24. package/dist/actionlog/sqliteBackend.js +105 -0
  25. package/dist/actionlog/toolContext.d.ts +36 -0
  26. package/dist/actionlog/toolContext.js +1 -0
  27. package/dist/actionlog/types.d.ts +62 -0
  28. package/dist/actionlog/types.js +62 -0
  29. package/dist/io/logging.js +1 -1
  30. package/dist/multisig/escrow.js +1 -1
  31. package/dist/scanning-syncing/backgroundWorker.js +1 -1
  32. package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.d.ts +1 -1
  33. package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.js +1 -1
  34. package/dist/scanning-syncing/blocksbuffer/blocksbufferCoordination.d.ts +1 -1
  35. package/dist/scanning-syncing/blocksbuffer/blocksbufferCoordination.js +2 -2
  36. package/dist/scanning-syncing/connectionStatus.d.ts +3 -1
  37. package/dist/scanning-syncing/connectionStatus.js +38 -3
  38. package/dist/scanning-syncing/connectionStatusOpened.d.ts +1 -1
  39. package/dist/scanning-syncing/connectionStatusOpened.js +2 -11
  40. package/dist/scanning-syncing/openWallet.js +1 -1
  41. package/dist/scanning-syncing/scanresult/computeKeyImage.d.ts +1 -1
  42. package/dist/scanning-syncing/scanresult/scanCache.d.ts +6 -2
  43. package/dist/scanning-syncing/scanresult/scanCache.js +12 -3
  44. package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +47 -2
  45. package/dist/scanning-syncing/scanresult/scanCacheOpened.js +328 -47
  46. package/dist/scanning-syncing/scanresult/scanCoordination.d.ts +3 -2
  47. package/dist/scanning-syncing/scanresult/scanCoordination.js +42 -6
  48. package/dist/scanning-syncing/scanresult/scanLoop.d.ts +1 -1
  49. package/dist/scanning-syncing/scanresult/scanLoop.js +1 -1
  50. package/dist/scanning-syncing/scanresult/scanResult.d.ts +1 -1
  51. package/dist/scanning-syncing/scanresult/scanResult.js +18 -13
  52. package/dist/scanning-syncing/scanresult/scanStats.d.ts +1 -1
  53. package/dist/scanning-syncing/scanresult/scanStats.js +9 -6
  54. package/dist/scanning-syncing/worker-entrypoints/worker.js +1 -1
  55. package/dist/scanning-syncing/worker-mains/cpubound-main.js +1 -1
  56. package/dist/scanning-syncing/worker-mains/multisig-main.d.ts +1 -1
  57. package/dist/scanning-syncing/worker-mains/multisig-main.js +1 -1
  58. package/dist/scanning-syncing/worker-mains/worker.js +1 -1
  59. package/dist/scansettings/scanSettings.js +1 -1
  60. package/dist/scansettings/scanSettingsOpened.d.ts +1 -1
  61. package/dist/scansettings/scanSettingsOpened.js +1 -1
  62. package/dist/send-functionality/inputSelection.d.ts +1 -1
  63. package/dist/send-functionality/transactionBuilding.d.ts +1 -1
  64. package/dist/tools/calls/001.d.ts +20 -0
  65. package/dist/tools/calls/001.js +236 -0
  66. package/dist/tools/calls/002-content.d.ts +46 -0
  67. package/dist/tools/calls/002-content.js +135 -0
  68. package/dist/tools/calls/002-fail-cleanup.d.ts +27 -0
  69. package/dist/tools/calls/002-fail-cleanup.js +60 -0
  70. package/dist/tools/calls/002.d.ts +64 -0
  71. package/dist/tools/calls/002.js +353 -0
  72. package/dist/tools/content.d.ts +22 -0
  73. package/dist/tools/content.js +41 -0
  74. package/dist/tools/counterparty.d.ts +3 -0
  75. package/dist/tools/counterparty.js +1 -0
  76. package/dist/tools/download-public-suffix.d.ts +1 -0
  77. package/dist/tools/download-public-suffix.js +20 -0
  78. package/dist/tools/globals.d.ts +196 -0
  79. package/dist/tools/globals.js +14 -0
  80. package/dist/tools/monero-tools-content.d.ts +29 -0
  81. package/dist/tools/monero-tools-content.js +80 -0
  82. package/dist/tools/monero-tools.d.ts +27 -52
  83. package/dist/tools/monero-tools.js +127 -222
  84. package/dist/tools/public-suffix-list.d.ts +1 -0
  85. package/dist/tools/public-suffix-list.js +10336 -0
  86. package/dist/tools/runtimeMessages.d.ts +4 -0
  87. package/dist/tools/runtimeMessages.js +18 -0
  88. package/dist/tools/toolActionLogContext.d.ts +33 -0
  89. package/dist/tools/toolActionLogContext.js +1 -0
  90. package/dist/tools/toolArm.d.ts +47 -0
  91. package/dist/tools/toolArm.js +1 -0
  92. package/dist/tools/ui.d.ts +2 -0
  93. package/dist/tools/ui.js +1 -0
  94. package/dist/tools/worker.d.ts +1 -0
  95. package/dist/tools/worker.js +1 -0
  96. package/dist/viewpair/ViewPair.d.ts +3 -3
  97. package/dist/viewpair/ViewPair.js +2 -2
  98. package/dist/wasm-processing/wasmFile.js +2 -2
  99. package/dist/{api.d.ts → xmr3.d.ts} +4 -0
  100. package/dist/{api.js → xmr3.js} +3 -0
  101. package/package.json +13 -6
@@ -1,10 +1,12 @@
1
- import { get_info, NodeUrl, signTransaction, ViewPair, } from "../../api";
1
+ import { get_info, NodeUrl, signTransaction, ViewPair, } from "../../xmr3";
2
2
  import { prepareInput, sumPayments, } from "../../send-functionality/inputSelection";
3
3
  import { createWebworker } from "../backgroundWorker";
4
4
  import { spendable } from "./scanResult";
5
- import { getPathPrefix, readPrivateSpendKeyFromEnv, SCAN_SETTINGS_STORE_NAME_DEFAULT, SUB_ADDRESS_INDEX_DEFAULT_VALUE, walletSettingsPlusKeys, } from "../../api";
5
+ import { getPathPrefix, readPrivateSpendKeyFromEnv, SCAN_SETTINGS_STORE_NAME_DEFAULT, SUB_ADDRESS_INDEX_DEFAULT_VALUE, walletSettingsPlusKeys, } from "../../xmr3";
6
6
  import { ScanSettingsOpened } from "../../scansettings/scanSettingsOpened";
7
7
  import { ConnectionStatusOpened } from "../connectionStatusOpened";
8
+ import { ActionLogOpened, } from "../../actionlog/actionLogOpened";
9
+ import { sendToBackground } from "../../tools/globals";
8
10
  import { findRange, lastRange, readCacheFileDefaultLocation, writeCacheFileDefaultLocationThrows, } from "./scanCache";
9
11
  import { alignScanStatsWithCache, isSelfSpent, processTxlogInputs, processTxlogPayments, writeStatsFileDefaultLocation, } from "./scanStats";
10
12
  import { readWriteConnectionStatusFile, } from "../connectionStatus";
@@ -389,12 +391,14 @@ export class ScanCacheOpened {
389
391
  let maybeInputs = [];
390
392
  let maybeFeeEstimate;
391
393
  let maybeSendResult;
394
+ let maybeSignedTx;
392
395
  try {
393
396
  const { selectedInputs, feeEstimate } = await this.calculateFeeAndSelectInputs(params);
394
397
  maybeInputs = selectedInputs;
395
398
  maybeFeeEstimate = feeEstimate;
396
399
  const unsignedTx = await this.makeTransactionFromSelectedInputs(params.payments, selectedInputs, feeEstimate);
397
400
  const signedTx = await this.signTransaction(unsignedTx);
401
+ maybeSignedTx = signedTx;
398
402
  const sendResult = await this.sendTransaction(signedTx);
399
403
  maybeSendResult = sendResult;
400
404
  if (sendResult.status !== "OK")
@@ -419,6 +423,8 @@ export class ScanCacheOpened {
419
423
  inputs_index,
420
424
  height: this.current_height,
421
425
  timestamp: Date.now(),
426
+ invocationId: params.invocationId,
427
+ signed_tx: signedTx,
422
428
  };
423
429
  const newLen = cache.tx_logs.push(txLog);
424
430
  const txLogIndex = newLen - 1;
@@ -464,17 +470,16 @@ export class ScanCacheOpened {
464
470
  inputs_index,
465
471
  height: this.current_height,
466
472
  timestamp: Date.now(),
473
+ invocationId: params.invocationId,
474
+ signed_tx: maybeSignedTx,
467
475
  };
468
- const newLen = cache.tx_logs.push(txLog);
476
+ cache.tx_logs.push(txLog);
469
477
  },
470
478
  });
471
479
  const newCache = await readCacheFileDefaultLocation(this.primary_address, this.pathPrefix);
472
480
  if (!newCache)
473
481
  throw new Error(`cache not found for primary address: ${this.primary_address}, and path prefix: ${this.pathPrefix}`);
474
- const changed_outputs = maybeInputs.map((input) => ({
475
- change_reason: "spent",
476
- output: input,
477
- }));
482
+ const changed_outputs = [];
478
483
  await this.feed({
479
484
  newCache,
480
485
  changed_outputs,
@@ -484,6 +489,179 @@ export class ScanCacheOpened {
484
489
  throw e;
485
490
  }
486
491
  }
492
+ outputsForIndexes(indexes) {
493
+ const byId = new Map(this.spendableInputs().map((output) => [
494
+ String(output.index_on_blockchain),
495
+ output,
496
+ ]));
497
+ const selected = [];
498
+ for (const id of indexes) {
499
+ const output = byId.get(id);
500
+ if (!output)
501
+ throw new Error(`input not spendable: ${id}`);
502
+ selected.push(output);
503
+ }
504
+ if (!selected.length)
505
+ throw new Error("no inputs selected");
506
+ return selected;
507
+ }
508
+ async makeSignSend(params) {
509
+ // an empty list means the wallet selects the inputs
510
+ if (!params.input_indexes.length) {
511
+ return this.makeSignSendTransaction({
512
+ payments: params.payments,
513
+ });
514
+ }
515
+ return this.makeSignSendTransaction({
516
+ payments: params.payments,
517
+ inputs: this.outputsForIndexes(params.input_indexes),
518
+ });
519
+ }
520
+ async sweepSignSend(params) {
521
+ const inputs = params.input_indexes.length
522
+ ? this.outputsForIndexes(params.input_indexes)
523
+ : this.spendableInputs();
524
+ if (!inputs.length)
525
+ throw new Error("no inputs selected");
526
+ const feeEstimate = await this.getFeeEstimate();
527
+ const unsignedTx = await this.makeSweepTransactionFromSelectedInputs(params.address, inputs, feeEstimate);
528
+ const signedTx = await this.signTransaction(unsignedTx);
529
+ const payments = [{ address: params.address, amount: "0" }];
530
+ const sendResult = await this.sendTransaction(signedTx);
531
+ if (sendResult.status !== "OK") {
532
+ await this.recordSend({
533
+ payments,
534
+ inputs,
535
+ signedTx,
536
+ feeEstimate,
537
+ sendResult,
538
+ error: "send raw transaction rpc returned error",
539
+ lockInputs: false,
540
+ });
541
+ throw new Error("send raw transaction rpc returned error");
542
+ }
543
+ await this.recordSend({
544
+ payments,
545
+ inputs,
546
+ signedTx,
547
+ feeEstimate,
548
+ sendResult,
549
+ lockInputs: true,
550
+ });
551
+ return sendResult;
552
+ }
553
+ async rebroadcastTx(txLogIndex) {
554
+ const txlog = this._cache.tx_logs?.[txLogIndex];
555
+ if (!txlog?.signed_tx)
556
+ throw new Error("no signed tx to rebroadcast");
557
+ const inputs = txlog.inputs_index
558
+ .map((id) => this._cache.outputs[id])
559
+ .filter((output) => output);
560
+ if (inputs.length &&
561
+ inputs.every((output) => typeof output.spent_in_tx_hash === "string"))
562
+ throw new Error("inputs already spent on chain");
563
+ const sendResult = await this.sendTransaction(txlog.signed_tx);
564
+ await this.stopWorker();
565
+ await writeCacheFileDefaultLocationThrows({
566
+ primary_address: this.primary_address,
567
+ pathPrefix: this.pathPrefix,
568
+ writeCallback: async (cache) => {
569
+ const row = cache.tx_logs?.[txLogIndex];
570
+ if (!row)
571
+ throw new Error("tx log missing");
572
+ row.sendResult = sendResult;
573
+ if (sendResult.status === "OK") {
574
+ row.error = undefined;
575
+ if (!cache.pending_spent_utxos)
576
+ cache.pending_spent_utxos = {};
577
+ for (const inputId of row.inputs_index)
578
+ cache.pending_spent_utxos[inputId] = txLogIndex;
579
+ }
580
+ else {
581
+ row.error = sendResult.reason || "rebroadcast failed";
582
+ }
583
+ },
584
+ });
585
+ const newCache = await readCacheFileDefaultLocation(this.primary_address, this.pathPrefix);
586
+ if (!newCache)
587
+ throw new Error("cache not found after rebroadcast");
588
+ await this.feed({ newCache, changed_outputs: [] });
589
+ await this.unpause();
590
+ return sendResult;
591
+ }
592
+ get failed_txs() {
593
+ const logs = this._cache.tx_logs || [];
594
+ const rows = [];
595
+ for (let index = 0; index < logs.length; index++) {
596
+ const txlog = logs[index];
597
+ if (!txlog || txlog.hidden_on_send_plate)
598
+ continue;
599
+ if (txlog.sendResult?.status === "OK")
600
+ continue;
601
+ const inputs = txlog.inputs_index
602
+ .map((id) => this._cache.outputs[id])
603
+ .filter((output) => output);
604
+ if (inputs.length &&
605
+ inputs.every((output) => typeof output.spent_in_tx_hash === "string"))
606
+ continue;
607
+ rows.push({ index, txlog, inputs });
608
+ }
609
+ return rows;
610
+ }
611
+ async recordSend(args) {
612
+ await this.stopWorker();
613
+ await writeCacheFileDefaultLocationThrows({
614
+ primary_address: this.primary_address,
615
+ pathPrefix: this.pathPrefix,
616
+ writeCallback: async (cache) => {
617
+ if (!cache.tx_logs)
618
+ cache.tx_logs = [];
619
+ if (!cache.pending_spent_utxos)
620
+ cache.pending_spent_utxos = {};
621
+ const inputs_index = args.inputs.map((input) => String(input.index_on_blockchain));
622
+ const txLog = {
623
+ sendResult: args.sendResult,
624
+ error: args.error,
625
+ feeEstimate: args.feeEstimate,
626
+ payments: args.payments,
627
+ node_url: this.node_url,
628
+ inputs_index,
629
+ height: this.current_height,
630
+ timestamp: Date.now(),
631
+ signed_tx: args.signedTx,
632
+ };
633
+ const txLogIndex = cache.tx_logs.push(txLog) - 1;
634
+ if (!args.lockInputs)
635
+ return;
636
+ for (const inputId of inputs_index)
637
+ cache.pending_spent_utxos[inputId] = txLogIndex;
638
+ },
639
+ });
640
+ const newCache = await readCacheFileDefaultLocation(this.primary_address, this.pathPrefix);
641
+ if (!newCache)
642
+ throw new Error("cache not found after send");
643
+ const changed_outputs = args.lockInputs
644
+ ? args.inputs.map((output) => ({ change_reason: "spent", output }))
645
+ : [];
646
+ await this.feed({ newCache, changed_outputs });
647
+ await this.unpause();
648
+ }
649
+ async dismissSendPlate() {
650
+ await this.stopWorker();
651
+ await writeCacheFileDefaultLocationThrows({
652
+ primary_address: this.primary_address,
653
+ pathPrefix: this.pathPrefix,
654
+ writeCallback: async (cache) => {
655
+ for (const row of cache.tx_logs || [])
656
+ row.hidden_on_send_plate = true;
657
+ },
658
+ });
659
+ const newCache = await readCacheFileDefaultLocation(this.primary_address, this.pathPrefix);
660
+ if (!newCache)
661
+ throw new Error("cache not found after dismiss");
662
+ await this.feed({ newCache, changed_outputs: [] });
663
+ await this.unpause();
664
+ }
487
665
  /**
488
666
  * makeStandardTransaction
489
667
  */
@@ -668,14 +846,17 @@ export class ScanCacheOpened {
668
846
  * for all primary addresses
669
847
  */
670
848
  async feed(params) {
671
- //TODO update aggregated amount stats + height
849
+ // same wallet must store the new cache before a listener reads transactions
850
+ const sameWallet = this.view_pair.primary_address === params.newCache.primary_address;
851
+ if (sameWallet) {
852
+ this._cache = params.newCache;
853
+ if (!this._no_stats)
854
+ this._stats = await alignScanStatsWithCache(this._cache, this.view_pair, this.primary_address, getPathPrefix(this.scan_settings_path, this.pathPrefix), this.subaddress_index, lastRange(this._cache.scanned_ranges)?.end);
855
+ }
672
856
  if (this.masterCacheChanged)
673
- this.masterCacheChanged(params);
674
- if (this.view_pair.primary_address !== params.newCache.primary_address)
857
+ await this.masterCacheChanged(params);
858
+ if (!sameWallet)
675
859
  return;
676
- this._cache = params.newCache;
677
- if (!this._no_stats)
678
- this._stats = await alignScanStatsWithCache(this._cache, this.view_pair, this.primary_address, getPathPrefix(this.scan_settings_path, this.pathPrefix), this.subaddress_index, lastRange(this._cache.scanned_ranges)?.end);
679
860
  for (const listener of this.notifyListeners) {
680
861
  if (listener)
681
862
  listener(params);
@@ -712,13 +893,17 @@ export class ScanCacheOpened {
712
893
  _stats = null;
713
894
  notifyListeners = [];
714
895
  }
896
+ function defaultNotifyMasterChanged(params) {
897
+ void sendToBackground("walletCacheChanged", JSON.stringify(params, (_k, v) => typeof v === "bigint" ? v.toString() : v)).catch(() => { });
898
+ }
715
899
  export class ManyScanCachesOpened {
716
900
  connectionStatusOpened;
901
+ actionLogOpened;
717
902
  _scanSettings;
718
903
  _options;
719
904
  get start_height() {
720
905
  if (this.wallets.length === 0)
721
- return null;
906
+ return this._scanSettings.start_height;
722
907
  return this.wallets[0]?.start_height;
723
908
  }
724
909
  // overall scan tip = lagging non-halted wallet (this.wallets is already non-halted)
@@ -735,14 +920,25 @@ export class ManyScanCachesOpened {
735
920
  }
736
921
  get node_url() {
737
922
  if (this.wallets.length === 0)
738
- return "";
923
+ return this._scanSettings.node_url ?? "";
739
924
  return this.wallets[0]?.node_url;
740
925
  }
741
926
  async changeNodeUrlAndStartHeight(node_url, start_height) {
742
- if (this.wallets.length === 0)
927
+ if (this.actionLogOpened.extensionMessageBus === "ui") {
928
+ await sendToBackground("worker.changeNodeUrlAndStartHeight", {
929
+ node_url,
930
+ start_height,
931
+ }).catch(() => { });
743
932
  return;
744
- const masterWallet = this.wallets[0];
745
- return await masterWallet.changeNodeUrlAndStartHeight(node_url, start_height);
933
+ }
934
+ if (this.wallets.length === 0) {
935
+ if (node_url !== undefined)
936
+ await this._scanSettings.setNodeUrl(node_url);
937
+ if (start_height !== undefined)
938
+ await this._scanSettings.setStartHeight(start_height);
939
+ return;
940
+ }
941
+ await this.wallets[0].changeNodeUrlAndStartHeight(node_url, start_height);
746
942
  }
747
943
  async retry() {
748
944
  if (this.wallets.length === 0)
@@ -751,6 +947,10 @@ export class ManyScanCachesOpened {
751
947
  return await masterWallet.retry();
752
948
  }
753
949
  async stopWorker() {
950
+ if (this.actionLogOpened.extensionMessageBus === "ui") {
951
+ await sendToBackground("worker.stopWorker", null).catch(() => { });
952
+ return;
953
+ }
754
954
  if (this.wallets.length === 0)
755
955
  return;
756
956
  const masterWallet = this.wallets[0];
@@ -764,10 +964,49 @@ export class ManyScanCachesOpened {
764
964
  return await this.wallets[0].dumpWorkerHeaps(dir);
765
965
  }
766
966
  async changeNodeUrl(node_url) {
767
- if (this.wallets.length === 0)
967
+ if (this.actionLogOpened.extensionMessageBus === "ui") {
968
+ await sendToBackground("worker.setNodeUrl", { node_url }).catch(() => { });
768
969
  return;
769
- const masterWallet = this.wallets[0];
770
- return await masterWallet.changeNodeUrl(node_url);
970
+ }
971
+ if (this.wallets.length === 0) {
972
+ await this._scanSettings.setNodeUrl(node_url);
973
+ return;
974
+ }
975
+ await this.wallets[0].changeNodeUrl(node_url);
976
+ }
977
+ walletByAddress(primary_address) {
978
+ const wallet = this.wallets.find((row) => row.primary_address === primary_address);
979
+ if (!wallet)
980
+ throw new Error("wallet not found");
981
+ return wallet;
982
+ }
983
+ async makeSignSend(params) {
984
+ if (this.actionLogOpened.extensionMessageBus === "ui") {
985
+ await sendToBackground("worker.makeSignSend", params).catch(() => { });
986
+ return;
987
+ }
988
+ return this.walletByAddress(params.primary_address).makeSignSend(params);
989
+ }
990
+ async sweepSignSend(params) {
991
+ if (this.actionLogOpened.extensionMessageBus === "ui") {
992
+ await sendToBackground("worker.sweepSignSend", params).catch(() => { });
993
+ return;
994
+ }
995
+ return this.walletByAddress(params.primary_address).sweepSignSend(params);
996
+ }
997
+ async rebroadcastTx(params) {
998
+ if (this.actionLogOpened.extensionMessageBus === "ui") {
999
+ await sendToBackground("worker.rebroadcastTx", params).catch(() => { });
1000
+ return;
1001
+ }
1002
+ return this.walletByAddress(params.primary_address).rebroadcastTx(params.tx_log_index);
1003
+ }
1004
+ async dismissSendPlate(params) {
1005
+ if (this.actionLogOpened.extensionMessageBus === "ui") {
1006
+ await sendToBackground("worker.dismissSendPlate", params).catch(() => { });
1007
+ return;
1008
+ }
1009
+ return this.walletByAddress(params.primary_address).dismissSendPlate();
771
1010
  }
772
1011
  get merchant_confirmations() {
773
1012
  if (this.wallets.length === 0)
@@ -811,7 +1050,20 @@ export class ManyScanCachesOpened {
811
1050
  return await this.wallets[0].setCpuWorkerCount(cpu_worker_count);
812
1051
  }
813
1052
  async setLogSettings(logs, logs_include, logs_exclude) {
814
- return await this._scanSettings.setLogSettings(logs, logs_include, logs_exclude);
1053
+ if (this.actionLogOpened.extensionMessageBus === "ui") {
1054
+ await sendToBackground("worker.setLogSettings", {
1055
+ logs,
1056
+ logs_include,
1057
+ logs_exclude,
1058
+ }).catch(() => { });
1059
+ }
1060
+ await this._scanSettings.setLogSettings(logs, logs_include, logs_exclude);
1061
+ if (this.actionLogOpened.extensionMessageBus === "ui")
1062
+ return;
1063
+ for (const wallet of this.wallets)
1064
+ await wallet.stopWorker();
1065
+ for (const wallet of this.wallets)
1066
+ await wallet.unpause();
815
1067
  }
816
1068
  async setWalletName(primary_address, name) {
817
1069
  await this._scanSettings.setWalletName(primary_address, name);
@@ -820,10 +1072,17 @@ export class ManyScanCachesOpened {
820
1072
  await this._scanSettings.setWalletSlot(primary_address, slot);
821
1073
  }
822
1074
  async changeStartHeight(start_height) {
823
- if (this.wallets.length === 0)
1075
+ if (this.actionLogOpened.extensionMessageBus === "ui") {
1076
+ await sendToBackground("worker.setStartHeight", {
1077
+ start_height,
1078
+ }).catch(() => { });
824
1079
  return;
825
- const masterWallet = this.wallets[0];
826
- return await masterWallet.changeStartHeight(start_height);
1080
+ }
1081
+ if (this.wallets.length === 0) {
1082
+ await this._scanSettings.setStartHeight(start_height);
1083
+ return;
1084
+ }
1085
+ await this.wallets[0].changeStartHeight(start_height);
827
1086
  }
828
1087
  static async _buildWallets(scanSettingsOpened, options) {
829
1088
  const { scan_settings_path, pathPrefix, no_worker, no_stats, workerError, notifyMasterChanged, } = options;
@@ -894,34 +1153,36 @@ export class ManyScanCachesOpened {
894
1153
  // wrap workerError with auto-retry
895
1154
  let retryTimer;
896
1155
  let instance = null;
1156
+ const clearRetryTimer = () => {
1157
+ clearTimeout(retryTimer);
1158
+ retryTimer = undefined;
1159
+ };
897
1160
  const retryFn = async () => {
1161
+ clearRetryTimer();
898
1162
  // no wallets means idle, do not rebuild or restart workers
899
- if (!instance || instance.wallets.length === 0) {
900
- clearTimeout(retryTimer);
901
- retryTimer = undefined;
902
- return;
903
- }
904
- await instance.buildWallets();
905
- // still empty after rebuild, stay idle
906
- if (instance.wallets.length === 0) {
907
- clearTimeout(retryTimer);
908
- retryTimer = undefined;
1163
+ if (!instance || instance.wallets.length === 0)
909
1164
  return;
910
- }
911
1165
  const node_url = instance.node_url;
912
1166
  if (!node_url)
913
- throw new Error("No nodeurl set, can't retry connection");
1167
+ return;
1168
+ // probe first; only restart workers when node is reachable
914
1169
  const getinfo_result = await get_info(node_url)
915
- .then((r) => {
916
- r?.status === "OK";
917
- })
1170
+ .then((r) => r?.status === "OK")
918
1171
  .catch(() => false);
919
- if (getinfo_result)
920
- await instance?.retry();
921
- clearTimeout(retryTimer);
922
- retryTimer = undefined;
1172
+ if (!getinfo_result) {
1173
+ // still down: try again later without buildWallets thrash
1174
+ if (!retryTimer && instance.wallets.length > 0) {
1175
+ retryTimer = setTimeout(retryFn, retryDelayMs ?? 5000);
1176
+ }
1177
+ return;
1178
+ }
1179
+ await instance.buildWallets();
923
1180
  };
924
1181
  const newOptions = { ...options };
1182
+ // browser worker (background.ts): broadcast cache to sidebar
1183
+ if (!newOptions.notifyMasterChanged && !newOptions.no_worker) {
1184
+ newOptions.notifyMasterChanged = defaultNotifyMasterChanged;
1185
+ }
925
1186
  let connectionFailedShown = false;
926
1187
  if (autoRetry) {
927
1188
  const originalError = options.workerError;
@@ -930,7 +1191,7 @@ export class ManyScanCachesOpened {
930
1191
  const msg = err instanceof Error ? err.message : String(err);
931
1192
  if (csOpened.connectionStatus?.last_packet?.status ===
932
1193
  "catastrophic_reorg") {
933
- clearTimeout(retryTimer);
1194
+ clearRetryTimer();
934
1195
  instance?.stopWorker();
935
1196
  throw new Error("catastrophic reorg, aborting ...");
936
1197
  }
@@ -960,10 +1221,22 @@ export class ManyScanCachesOpened {
960
1221
  }
961
1222
  : (onConnectionStatusChange ?? null));
962
1223
  csOpened.watch(connectionStatusIntervalMs);
963
- instance = new ManyScanCachesOpened(wallets, csOpened, scanSettingsOpened, newOptions);
1224
+ const actionLogPath = newOptions.actionLogPath ??
1225
+ actionLogPathFromScanSettings(scan_settings_path || SCAN_SETTINGS_STORE_NAME_DEFAULT);
1226
+ const actionLogOpened = await ActionLogOpened.create({
1227
+ path: actionLogPath,
1228
+ backend: newOptions.actionLogBackend,
1229
+ onChange: newOptions.onActionLogChange ?? null,
1230
+ extensionMessageBus: newOptions.extensionMessageBus,
1231
+ });
1232
+ instance = new ManyScanCachesOpened(wallets, csOpened, actionLogOpened, scanSettingsOpened, newOptions);
964
1233
  return instance;
965
1234
  }
966
1235
  async buildWallets() {
1236
+ if (this.actionLogOpened.extensionMessageBus === "ui") {
1237
+ await sendToBackground("worker.buildWallets", null).catch(() => { });
1238
+ return;
1239
+ }
967
1240
  await this.stopWorker();
968
1241
  await this.reloadWalletsAfterStop();
969
1242
  }
@@ -1003,10 +1276,18 @@ export class ManyScanCachesOpened {
1003
1276
  get wallets() {
1004
1277
  return this._wallets;
1005
1278
  }
1006
- constructor(wallets, connectionStatusOpened, _scanSettings, _options) {
1279
+ constructor(wallets, connectionStatusOpened, actionLogOpened, _scanSettings, _options) {
1007
1280
  this.connectionStatusOpened = connectionStatusOpened;
1281
+ this.actionLogOpened = actionLogOpened;
1008
1282
  this._scanSettings = _scanSettings;
1009
1283
  this._options = _options;
1010
1284
  this._wallets = wallets;
1285
+ this.actionLogOpened.bindMco(this);
1011
1286
  }
1012
1287
  }
1288
+ function actionLogPathFromScanSettings(scan_settings_path) {
1289
+ const base = scan_settings_path.replace(/\.json$/i, "");
1290
+ if (globalThis.areWeInTheBrowser === true)
1291
+ return "actionlog";
1292
+ return `${base}-actionlog.sqlite`;
1293
+ }
@@ -1,7 +1,7 @@
1
- import { type GetBlocksBinBufferItem, type BlocksBufferLoopResult, type BlocksBufferIteratorResult, type ProcessScanResult, type BlockInfo } from "../../api";
1
+ import { type GetBlocksBinBufferItem, type BlocksBufferLoopResult, type BlocksBufferIteratorResult, type ProcessScanResult, type BlockInfo } from "../../xmr3";
2
2
  import { type ScanLoopYield } from "./scanLoop";
3
3
  import { type WorkItem } from "./scanLoop";
4
- import { type ScanSettings } from "../../api";
4
+ import { type ScanSettings } from "../../xmr3";
5
5
  import { type CacheRange, type ScanCache } from "./scanCache";
6
6
  export type WalletConfig = {
7
7
  primary_address: string;
@@ -29,6 +29,7 @@ export type WorkToBeDone = {
29
29
  * @param scan_settings_path
30
30
  */
31
31
  export declare function findWorkToBeDone(scan_settings_path?: string, pathPrefix?: string): Promise<WorkToBeDone | false>;
32
+ export declare function fastForwardFetch(wallet_configs: WalletConfigPlusCache[], start_height: number, batchEnd: number): CacheRange | null;
32
33
  export declare function workToBeDoneForBatch(cache: ScanCache, batch_meta_infos: BlockInfo[]): "skip" | {
33
34
  from: number;
34
35
  };
@@ -1,8 +1,8 @@
1
- import { setupBlocksBufferGenerator, ViewPair, handleConnectionStatusChanges, processScanResult, writeCacheToFile, sleep, findTipIndex, readWriteConnectionStatusFile, applyWalletScanProgress, } from "../../api";
1
+ import { setupBlocksBufferGenerator, ViewPair, handleConnectionStatusChanges, processScanResult, writeCacheToFile, sleep, findTipIndex, readWriteConnectionStatusFile, applyWalletScanProgress, } from "../../xmr3";
2
2
  import {} from "./scanLoop";
3
3
  import { makeWorkItem } from "./scanLoop";
4
- import { cullTooLargeScanHeight, getNonHaltedWallets, getPathPrefix, openScanSettingsFile, SCAN_SETTINGS_STORE_NAME_DEFAULT, walletSettingsPlusKeys, } from "../../api";
5
- import { findRange, initScanCacheFile, lastRange, makeCacheRangeForHeight, mergeRanges, } from "./scanCache";
4
+ import { cullTooLargeScanHeight, getNonHaltedWallets, getPathPrefix, openScanSettingsFile, SCAN_SETTINGS_STORE_NAME_DEFAULT, walletSettingsPlusKeys, } from "../../xmr3";
5
+ import { currentScanHeightFromRanges, findRange, initScanCacheFile, lastRange, makeCacheRangeForHeight, mergeRanges, } from "./scanCache";
6
6
  import { sendToCpuWorker } from "../worker-mains/cpubound-main";
7
7
  import { log } from "../../io/logging";
8
8
  /**
@@ -79,6 +79,19 @@ export async function findWorkToBeDone(scan_settings_path = SCAN_SETTINGS_STORE_
79
79
  scan_settings,
80
80
  };
81
81
  }
82
+ // slowest wallet past this batch: return its range so fetch can restart at that tip.
83
+ export function fastForwardFetch(wallet_configs, start_height, batchEnd) {
84
+ if (!wallet_configs.length)
85
+ return null;
86
+ const slowest = Math.min(...wallet_configs.map((w) => currentScanHeightFromRanges(w.cache.scanned_ranges, start_height)));
87
+ if (slowest <= batchEnd)
88
+ return null;
89
+ const owner = wallet_configs.find((w) => currentScanHeightFromRanges(w.cache.scanned_ranges, start_height) ===
90
+ slowest);
91
+ return owner
92
+ ? findRange(owner.cache.scanned_ranges, start_height)
93
+ : null;
94
+ }
82
95
  export function workToBeDoneForBatch(cache, batch_meta_infos) {
83
96
  const begin_height = batch_meta_infos[0].block_height;
84
97
  const end_height = batch_meta_infos[batch_meta_infos.length - 1].block_height;
@@ -278,6 +291,12 @@ export async function setupCoordinator(scanSettingsPath, pathPrefix, stopSync) {
278
291
  const work_to_be_done = await findWorkToBeDone(scanSettingsPath, pathPrefix);
279
292
  if (!work_to_be_done)
280
293
  return false;
294
+ // bring connection status file scan height in line with / cache file / in memory state
295
+ await readWriteConnectionStatusFile((cs) => {
296
+ applyWalletScanProgress(cs, {
297
+ current_scan_height: currentScanHeightFromRanges(work_to_be_done.wallet_configs[0]?.cache.scanned_ranges ?? [], work_to_be_done.scan_settings.start_height || 0),
298
+ });
299
+ }, scanSettingsPath);
281
300
  const { generator: blocksGenerator, blocksBuffer } = await setupBlocksBufferGenerator({
282
301
  nodeUrl: work_to_be_done.scan_settings.node_url,
283
302
  startHeight: work_to_be_done.start_height,
@@ -345,9 +364,10 @@ export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix
345
364
  if (!ctx)
346
365
  throw new Error("[coordinatorMain Multithreaded] findWorkToBeDone returned false");
347
366
  const work_to_be_done = ctx.work_to_be_done;
348
- const blocksBuffer = ctx.blocksBuffer;
367
+ let blocksBuffer = ctx.blocksBuffer;
349
368
  const workBuffer = ctx.workBuffer;
350
- const blocksGenerator = ctx.blocksGenerator;
369
+ let blocksGenerator = ctx.blocksGenerator;
370
+ let fetchAt = work_to_be_done.start_height;
351
371
  let totalBlocksScanned = 0;
352
372
  let scanStartTime = Date.now();
353
373
  let blocksPromise = blocksGenerator.next();
@@ -428,12 +448,28 @@ export async function* coordinatorMainMultithreaded(scanSettingsPath, pathPrefix
428
448
  const blockCount = to_be_processed.to - to_be_processed.from + 1;
429
449
  totalBlocksScanned += blockCount;
430
450
  const res = await processWorkItem(to_be_processed, workBuffer, blocksBuffer, getPathPrefix(scanSettingsPath, pathPrefix), wallet.secret_spend_key);
451
+ const jumped = fastForwardFetch(work_to_be_done.wallet_configs, work_to_be_done.scan_settings.start_height || 0, to_be_processed.batch.get_blocks_result_meta.block_infos[to_be_processed.to].block_height);
452
+ if (jumped && jumped.end > fetchAt) {
453
+ log("coordinatorMainMultithreaded", ["jump", fetchAt, "->", jumped.end]);
454
+ await blocksGenerator.return(undefined);
455
+ const next = await setupBlocksBufferGenerator({
456
+ nodeUrl: work_to_be_done.scan_settings.node_url,
457
+ startHeight: jumped.end,
458
+ anchor_range: jumped,
459
+ scanSettingsPath,
460
+ stopSync,
461
+ });
462
+ blocksBuffer = next.blocksBuffer;
463
+ blocksGenerator = next.generator;
464
+ blocksPromise = blocksGenerator.next();
465
+ fetchAt = jumped.end;
466
+ }
431
467
  // always persist wallet progress after process; eta only if we have a new one
432
468
  // so missing eta does not wipe the previous value (no flicker)
433
469
  const eta = computeETA(wallet.cache, totalBlocksScanned, scanStartTime);
434
470
  await readWriteConnectionStatusFile((cs) => {
435
471
  applyWalletScanProgress(cs, {
436
- current_scan_height: lastRange(wallet.cache.scanned_ranges)?.end || 0,
472
+ current_scan_height: currentScanHeightFromRanges(wallet.cache.scanned_ranges, work_to_be_done.scan_settings.start_height || 0),
437
473
  scanned_ranges: wallet.cache.scanned_ranges,
438
474
  daemon_height: wallet.cache.daemon_height,
439
475
  eta,
@@ -1,4 +1,4 @@
1
- import { type GetBlocksBinBufferItem, type ScanResult, type WalletConfig, type WalletConfigPlusCache } from "../../api";
1
+ import { type GetBlocksBinBufferItem, type ScanResult, type WalletConfig, type WalletConfigPlusCache } from "../../xmr3";
2
2
  export type WorkItem = {
3
3
  walletConfig: WalletConfigPlusCache;
4
4
  batch: GetBlocksBinBufferItem;
@@ -1,4 +1,4 @@
1
- import { sleep, ViewPair, } from "../../api";
1
+ import { sleep, ViewPair, } from "../../xmr3";
2
2
  export function makeWorkItem(walletConfig, batch, from, to) {
3
3
  if (to && to > batch.get_blocks_result_meta.block_infos.length - 1) {
4
4
  // we could throw here but we dont. Off by one errors shouldnt break the application
@@ -1,4 +1,4 @@
1
- import { type BlockInfo, type Output } from "../../api";
1
+ import { type BlockInfo, type Output } from "../../xmr3";
2
2
  import { type KeyImage } from "./computeKeyImage";
3
3
  import { type ErrorResponse } from "../../node-interaction/binaryEndpoints";
4
4
  import type { CacheChangedCallback, CacheRange, ChangedOutput, ScanCache } from "./scanCache";