@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.
- package/README.md +80 -8
- package/dist/api.d.ts +4 -0
- package/dist/api.js +4 -0
- package/dist/io/logging.d.ts +4 -4
- package/dist/io/logging.js +2 -0
- package/dist/keypairs-seeds/keypairs.d.ts +8 -0
- package/dist/keypairs-seeds/keypairs.js +31 -3
- package/dist/multisig/DistributedKeyGenerator.d.ts +125 -0
- package/dist/multisig/DistributedKeyGenerator.js +197 -0
- package/dist/multisig/MultiSigTxSigner.d.ts +68 -0
- package/dist/multisig/MultiSigTxSigner.js +123 -0
- package/dist/multisig/escrow.d.ts +58 -0
- package/dist/multisig/escrow.js +104 -0
- package/dist/multisig/multisig.d.ts +36 -0
- package/dist/multisig/multisig.js +140 -0
- package/dist/node-interaction/nodeUrl.js +2 -1
- package/dist/scanning-syncing/backgroundWorker.d.ts +1 -0
- package/dist/scanning-syncing/backgroundWorker.js +1 -1
- package/dist/scanning-syncing/scanSettings.d.ts +1 -0
- package/dist/scanning-syncing/scanSettings.js +9 -3
- package/dist/scanning-syncing/scanresult/computeKeyImage.d.ts +2 -1
- package/dist/scanning-syncing/scanresult/computeKeyImage.js +4 -2
- package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +11 -0
- package/dist/scanning-syncing/scanresult/scanCacheOpened.js +104 -11
- package/dist/scanning-syncing/scanresult/scanResult.js +7 -3
- package/dist/scanning-syncing/worker-entrypoints/worker.js +1 -1
- package/dist/scanning-syncing/worker-mains/multisig-main.d.ts +13 -0
- package/dist/scanning-syncing/worker-mains/multisig-main.js +31 -0
- package/dist/scanning-syncing/worker-mains/worker.js +12 -0
- package/dist/send-functionality/multisig.d.ts +72 -0
- package/dist/send-functionality/multisig.js +97 -0
- package/dist/send-functionality/transactionBuilding.d.ts +1 -0
- package/dist/send-functionality/transactionBuilding.js +23 -2
- package/dist/viewpair/ViewPair.d.ts +8 -0
- package/dist/viewpair/ViewPair.js +16 -8
- package/dist/wasm-processing/wasmFile.d.ts +1 -0
- package/dist/wasm-processing/wasmFile.js +8 -3
- package/dist/wasm-processing/wasmProcessor.d.ts +2 -2
- package/dist/wasm-processing/wasmProcessor.js +4 -5
- 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.
|
|
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
|
|
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.
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
stats
|
|
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
|
-
|
|
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
|
|
1
|
+
import { CatastrophicReorgError } from "../../api";
|
|
2
2
|
import { computeKeyImage } from "./computeKeyImage";
|
|
3
3
|
import { log } from "../../io/logging";
|
|
4
|
-
import { mergeRanges, findRange,
|
|
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) {
|