@spirobel/monero-wallet-api 0.4.1 → 0.5.0

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 (40) hide show
  1. package/README.md +80 -8
  2. package/dist/api.d.ts +4 -0
  3. package/dist/api.js +4 -0
  4. package/dist/io/logging.d.ts +4 -4
  5. package/dist/io/logging.js +2 -0
  6. package/dist/keypairs-seeds/keypairs.d.ts +8 -0
  7. package/dist/keypairs-seeds/keypairs.js +31 -3
  8. package/dist/multisig/DistributedKeyGenerator.d.ts +125 -0
  9. package/dist/multisig/DistributedKeyGenerator.js +197 -0
  10. package/dist/multisig/MultiSigTxSigner.d.ts +68 -0
  11. package/dist/multisig/MultiSigTxSigner.js +123 -0
  12. package/dist/multisig/escrow.d.ts +58 -0
  13. package/dist/multisig/escrow.js +104 -0
  14. package/dist/multisig/multisig.d.ts +36 -0
  15. package/dist/multisig/multisig.js +140 -0
  16. package/dist/node-interaction/nodeUrl.js +2 -1
  17. package/dist/scanning-syncing/backgroundWorker.d.ts +1 -0
  18. package/dist/scanning-syncing/backgroundWorker.js +1 -1
  19. package/dist/scanning-syncing/scanSettings.d.ts +1 -0
  20. package/dist/scanning-syncing/scanSettings.js +9 -3
  21. package/dist/scanning-syncing/scanresult/computeKeyImage.d.ts +2 -1
  22. package/dist/scanning-syncing/scanresult/computeKeyImage.js +4 -2
  23. package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +11 -0
  24. package/dist/scanning-syncing/scanresult/scanCacheOpened.js +104 -11
  25. package/dist/scanning-syncing/scanresult/scanResult.js +7 -3
  26. package/dist/scanning-syncing/worker-entrypoints/worker.js +1 -1
  27. package/dist/scanning-syncing/worker-mains/multisig-main.d.ts +13 -0
  28. package/dist/scanning-syncing/worker-mains/multisig-main.js +31 -0
  29. package/dist/scanning-syncing/worker-mains/worker.js +12 -0
  30. package/dist/send-functionality/multisig.d.ts +72 -0
  31. package/dist/send-functionality/multisig.js +97 -0
  32. package/dist/send-functionality/transactionBuilding.d.ts +1 -0
  33. package/dist/send-functionality/transactionBuilding.js +23 -2
  34. package/dist/viewpair/ViewPair.d.ts +8 -0
  35. package/dist/viewpair/ViewPair.js +16 -8
  36. package/dist/wasm-processing/wasmFile.d.ts +1 -0
  37. package/dist/wasm-processing/wasmFile.js +8 -3
  38. package/dist/wasm-processing/wasmProcessor.d.ts +2 -2
  39. package/dist/wasm-processing/wasmProcessor.js +4 -5
  40. package/package.json +6 -2
@@ -2,7 +2,7 @@ import { atomicWrite, NodeUrl, signTransaction, ViewPair, } from "../../api";
2
2
  import { prepareInput, sumPayments, } from "../../send-functionality/inputSelection";
3
3
  import { createWebworker } from "../backgroundWorker";
4
4
  import { spendable } from "./scanResult";
5
- import { openScanSettingsFile, readPrivateSpendKeyFromEnv, readWalletFromScanSettings, SCAN_SETTINGS_STORE_NAME_DEFAULT, walletSettingsPlusKeys, writeNodeUrlToScanSettings, writeStartHeightToScanSettings, writeWalletToScanSettings, } from "../scanSettings";
5
+ import { getPathPrefix, openScanSettingsFile, readPrivateSpendKeyFromEnv, readWalletFromScanSettings, SCAN_SETTINGS_STORE_NAME_DEFAULT, SUB_ADDRESS_INDEX_DEFAULT_VALUE, walletSettingsPlusKeys, writeNodeUrlToScanSettings, writeStartHeightToScanSettings, writeWalletToScanSettings, } from "../scanSettings";
6
6
  import { findRange, lastRange, readCacheFileDefaultLocation, writeCacheFileDefaultLocationThrows, } from "./scanCache";
7
7
  import { alignScanStatsWithCache, isSelfSpent, processTxlogInputs, processTxlogPayments, writeStatsFileDefaultLocation, } from "./scanStats";
8
8
  import { updateSyncETA, readWriteConnectionStatusFile, } from "../connectionStatus";
@@ -53,19 +53,32 @@ export class ScanCacheOpened {
53
53
  // unpause will start scanning from this.wallet_scan_settings.start_height
54
54
  await scanCacheOpen.unpause();
55
55
  }
56
- scanCacheOpen._stats = await alignScanStatsWithCache(scanCacheOpen._cache, scanCacheOpen.view_pair, params.primary_address, params.pathPrefix, walletSettings.subaddress_index, lastRange(scanCacheOpen._cache.scanned_ranges)?.end);
56
+ scanCacheOpen._highest_subaddress_index =
57
+ walletSettings.subaddress_index || SUB_ADDRESS_INDEX_DEFAULT_VALUE;
58
+ if (!params.no_stats) {
59
+ scanCacheOpen._stats = await alignScanStatsWithCache(scanCacheOpen._cache, scanCacheOpen.view_pair, params.primary_address, getPathPrefix(params.scan_settings_path, params.pathPrefix), walletSettings.subaddress_index, lastRange(scanCacheOpen._cache.scanned_ranges)?.end);
60
+ }
61
+ else {
62
+ scanCacheOpen._no_stats = params.no_stats; // true
63
+ }
57
64
  return scanCacheOpen;
58
65
  }
59
66
  get start_height() {
60
67
  return this._start_height;
61
68
  }
69
+ get subaddress_index() {
70
+ if (typeof this._highest_subaddress_index === "undefined" ||
71
+ this._highest_subaddress_index === null)
72
+ return SUB_ADDRESS_INDEX_DEFAULT_VALUE;
73
+ return this._highest_subaddress_index;
74
+ }
62
75
  get current_height() {
63
76
  let current_range = findRange(this._cache.scanned_ranges, this._start_height || 0);
64
77
  return current_range?.end || null;
65
78
  }
66
79
  get current_top_range_height() {
67
80
  if (typeof this._stats === "undefined" || this._stats === null)
68
- return null;
81
+ return this.current_height;
69
82
  return this._stats.height;
70
83
  }
71
84
  async changeStartHeight(start_height) {
@@ -181,6 +194,21 @@ export class ScanCacheOpened {
181
194
  throw new Error("privateSpendKey not found in env");
182
195
  return await signTransaction(unsignedTx, privateSpendKey);
183
196
  }
197
+ async getFeeEstimate() {
198
+ const node = await NodeUrl.create(this.node_url);
199
+ const feeEstimate = await node.getFeeEstimate();
200
+ const feePerByte = BigInt(feeEstimate.fees[0]);
201
+ const max_plausible_fee = 20000000000n; // 0.02 XMR
202
+ const feeFor10kb = feePerByte * 10000n;
203
+ //2. check if fee is too high
204
+ if (feeFor10kb > max_plausible_fee) {
205
+ throw new Error(`fee too high:
206
+ ${feeFor10kb} (fee for 10kb tx size) > ${max_plausible_fee} (0.001 XMR)
207
+ most likely your node is faulty. connect to another node.
208
+ preferably run one yourself locally.`);
209
+ }
210
+ return feeEstimate;
211
+ }
184
212
  async calculateFeeAndSelectInputs(params) {
185
213
  const sum = sumPayments(params.payments);
186
214
  const node = await NodeUrl.create(this.node_url);
@@ -243,6 +271,49 @@ export class ScanCacheOpened {
243
271
  });
244
272
  return unsignedTx;
245
273
  }
274
+ async makeSweepTransactionFromSelectedInputs(destination_address, selectedInputs, feeEstimate) {
275
+ // 4. get output distribution
276
+ const node = await NodeUrl.create(this.node_url);
277
+ const distibution = await node.getOutputDistribution();
278
+ const inputs = [];
279
+ for (const input of selectedInputs) {
280
+ // 5. sample decoys & get outs from node: here is where a privacy compromising event could happen
281
+ const sizesToTry = this.decoyRetry
282
+ ? this.decoyRetrySizes
283
+ : [this.decoySampleCount];
284
+ let madeInput;
285
+ for (const size of sizesToTry) {
286
+ if (madeInput)
287
+ break;
288
+ try {
289
+ const prepared = prepareInput(node, distibution, input, size);
290
+ const wasmInput = node.makeInput(prepared.input, prepared.sample.candidates, await prepared.outsResponse);
291
+ madeInput = wasmInput;
292
+ }
293
+ catch (e) {
294
+ if (size === sizesToTry[sizesToTry.length - 1])
295
+ throw e;
296
+ // fall through to next size
297
+ }
298
+ }
299
+ if (!madeInput)
300
+ throw new Error("failed to make input");
301
+ inputs.push(madeInput);
302
+ }
303
+ // 7. make transaction: combine inputs, payments + fee info
304
+ const unsignedTx = this.view_pair.makeSweepTransaction({
305
+ inputs,
306
+ payments: [
307
+ {
308
+ address: destination_address,
309
+ amount: "0",
310
+ },
311
+ ],
312
+ fee_response: feeEstimate,
313
+ fee_priority: "unimportant",
314
+ });
315
+ return unsignedTx;
316
+ }
246
317
  /**
247
318
  * this function returns the unsigned transaction, throws {@link SendError}
248
319
  */
@@ -250,16 +321,30 @@ export class ScanCacheOpened {
250
321
  const { selectedInputs, feeEstimate } = await this.calculateFeeAndSelectInputs(params);
251
322
  return await this.makeTransactionFromSelectedInputs(params.payments, selectedInputs, feeEstimate);
252
323
  }
324
+ /**
325
+ * sweep inputs to external wallet address (the wallet will receive input amount - fee)
326
+ * @param inputs use spendableInputs() to find inputs to put in
327
+ */
328
+ async sweepToExternalWallet(destination_address, inputs) {
329
+ const feeEstimate = await this.getFeeEstimate();
330
+ return await this.makeSweepTransactionFromSelectedInputs(destination_address, inputs, feeEstimate);
331
+ }
253
332
  get daemon_height() {
254
333
  return this._cache.daemon_height;
255
334
  }
256
335
  get amount() {
336
+ if (this.no_stats)
337
+ throw new Error("instance has no_stats option active");
257
338
  return this._stats?.total_spendable_amount || 0n;
258
339
  }
259
340
  get pending_amount() {
341
+ if (this.no_stats)
342
+ throw new Error("instance has no_stats option active");
260
343
  return this._stats?.total_pending_amount || 0n;
261
344
  }
262
345
  get subaddresses() {
346
+ if (this.no_stats)
347
+ throw new Error("instance has no_stats option active");
263
348
  return Object.values(this._stats?.subaddresses || {});
264
349
  }
265
350
  get tx_logs() {
@@ -403,6 +488,7 @@ export class ScanCacheOpened {
403
488
  const last_subaddress_index = walletSettings.subaddress_index || 0;
404
489
  const minor = last_subaddress_index + 1;
405
490
  const subaddress = this.view_pair.makeSubaddress(minor);
491
+ this._highest_subaddress_index = minor;
406
492
  await writeWalletToScanSettings({
407
493
  primary_address: this.view_pair.primary_address,
408
494
  subaddress_index: minor,
@@ -417,13 +503,14 @@ export class ScanCacheOpened {
417
503
  created_at_timestamp,
418
504
  not_yet_included: true,
419
505
  };
420
- this._stats = await writeStatsFileDefaultLocation({
421
- primary_address: this.primary_address,
422
- pathPrefix: this.pathPrefix,
423
- writeCallback: async (stats) => {
424
- stats.subaddresses[minor.toString()] = new_subaddress;
425
- },
426
- });
506
+ if (!this._no_stats)
507
+ this._stats = await writeStatsFileDefaultLocation({
508
+ primary_address: this.primary_address,
509
+ pathPrefix: getPathPrefix(this.scan_settings_path, this.pathPrefix),
510
+ writeCallback: async (stats) => {
511
+ stats.subaddresses[minor.toString()] = new_subaddress;
512
+ },
513
+ });
427
514
  return new_subaddress;
428
515
  }
429
516
  /**
@@ -546,7 +633,8 @@ export class ScanCacheOpened {
546
633
  if (this.view_pair.primary_address !== params.newCache.primary_address)
547
634
  return;
548
635
  this._cache = params.newCache;
549
- this._stats = await alignScanStatsWithCache(this._cache, this.view_pair, this.primary_address, this.pathPrefix, undefined, lastRange(this._cache.scanned_ranges)?.end);
636
+ if (!this._no_stats)
637
+ 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);
550
638
  if (!this.no_worker) {
551
639
  const etaResult = await updateSyncETA(this._cache.daemon_height, this.current_height || 0, this.last_eta_height, this.last_eta_timestamp, this.scan_settings_path);
552
640
  this.last_eta_height = etaResult.last_height;
@@ -557,6 +645,11 @@ export class ScanCacheOpened {
557
645
  listener(params);
558
646
  }
559
647
  }
648
+ _highest_subaddress_index = null;
649
+ _no_stats = false;
650
+ get no_stats() {
651
+ return this._no_stats;
652
+ }
560
653
  _cache = {
561
654
  daemon_height: 0,
562
655
  outputs: {},
@@ -1,8 +1,10 @@
1
- import { CatastrophicReorgError, } from "../../api";
1
+ import { CatastrophicReorgError } from "../../api";
2
2
  import { computeKeyImage } from "./computeKeyImage";
3
3
  import { log } from "../../io/logging";
4
- import { mergeRanges, findRange, readCacheFileDefaultLocation, lastRange, writeCacheToFile, findRangeThrows, } from "./scanCache";
4
+ import { mergeRanges, findRange, findRangeThrows } from "./scanCache";
5
5
  import {} from "../../node-interaction/binaryEndpoints";
6
+ import { WasmProcessor } from "../../wasm-processing/wasmProcessor";
7
+ import { monero_wallet_api_wasm } from "../../wasm-processing/wasmFile";
6
8
  export async function processScanResult(params) {
7
9
  const { result, scanCache: cache, secret_spend_key } = params;
8
10
  let changed_outputs = [];
@@ -137,6 +139,7 @@ export function makeNewRange(newRange, cache) {
137
139
  // Assumption: result is new, cache is still old. (this + detectOwnspends() turns the catch new, based on the scan result)
138
140
  export async function detectOutputs(result, cache, spend_private_key) {
139
141
  let changed_outputs = [];
142
+ const wasmProcessor = await WasmProcessor.init(monero_wallet_api_wasm);
140
143
  for (const output of result.outputs) {
141
144
  // 0. prevent burning bug and avoid overwriting earlier found outputs
142
145
  const duplicate = Object.values(cache.outputs).find((ex) => ex.stealth_address === output.stealth_address && !ex.burned);
@@ -167,7 +170,7 @@ export async function detectOutputs(result, cache, spend_private_key) {
167
170
  changed_outputs.push({ output, change_reason: "added" });
168
171
  // 3. if this is not view only, add the key image to the cache, to find transactions spent by this wallet
169
172
  if (spend_private_key) {
170
- let keyImage = await computeKeyImage(output, spend_private_key);
173
+ let keyImage = await computeKeyImage(output, spend_private_key, wasmProcessor);
171
174
  if (keyImage) {
172
175
  cache.own_key_images[keyImage] = globalId;
173
176
  }
@@ -223,6 +226,7 @@ export function outputStatus(output, cache, current_height) {
223
226
  if (unlock_height > current_height) {
224
227
  return { status: "pending", unlock_height };
225
228
  }
229
+ // TODO: check that that output is not txlog input indeces
226
230
  return { status: "spendable" };
227
231
  }
228
232
  export function spendable(output, cache, current_height) {