@spirobel/monero-wallet-api 0.4.1 → 0.5.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.
- 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 +14 -13
- package/dist/scanning-syncing/scanSettings.js +35 -29
- 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
|
@@ -3,6 +3,10 @@ import { atomicWrite } from "../io/atomicWrite";
|
|
|
3
3
|
import { refreshEnvIndexedDB } from "../io/indexedDB";
|
|
4
4
|
import { LOCAL_NODE_DEFAULT_URL } from "../node-interaction/nodeUrl";
|
|
5
5
|
export const SCAN_SETTINGS_STORE_NAME_DEFAULT = "ScanSettings.json";
|
|
6
|
+
// default value for subaddress highest minor index eg up to which minor index,
|
|
7
|
+
// to add subaddresses to the scanner
|
|
8
|
+
// (we never touch major index, having two values is just too confusing)
|
|
9
|
+
export const SUB_ADDRESS_INDEX_DEFAULT_VALUE = 1;
|
|
6
10
|
/**
|
|
7
11
|
* Writes scan settings to the default or specified storage file in json.
|
|
8
12
|
*
|
|
@@ -19,15 +23,15 @@ export const SCAN_SETTINGS_STORE_NAME_DEFAULT = "ScanSettings.json";
|
|
|
19
23
|
* ```
|
|
20
24
|
*
|
|
21
25
|
* @param scan_settings - The complete {@link ScanSettings} configuration to persist.
|
|
22
|
-
* @param
|
|
26
|
+
* @param scan_settings_path - Optional path for the settings file. Defaults to `SCAN_SETTINGS_STORE_NAME_DEFAULT`.
|
|
23
27
|
* @returns A promise that resolves when the file is successfully written.
|
|
24
28
|
* @throws Will throw if file writing fails (e.g., permissions, disk space).
|
|
25
29
|
*/
|
|
26
|
-
export async function writeScanSettings(scan_settings,
|
|
27
|
-
return await atomicWrite(
|
|
30
|
+
export async function writeScanSettings(scan_settings, scan_settings_path = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
|
|
31
|
+
return await atomicWrite(scan_settings_path, JSON.stringify(scan_settings, null, 2));
|
|
28
32
|
}
|
|
29
33
|
export async function writeScanSettingsFileDefaultLocation(params) {
|
|
30
|
-
let settings = await openScanSettingsFile(params.
|
|
34
|
+
let settings = await openScanSettingsFile(params.scan_settings_path);
|
|
31
35
|
if (!settings) {
|
|
32
36
|
// Create empty ScanSettings object with default values
|
|
33
37
|
settings = {
|
|
@@ -38,10 +42,10 @@ export async function writeScanSettingsFileDefaultLocation(params) {
|
|
|
38
42
|
}
|
|
39
43
|
await params.writeCallback(settings);
|
|
40
44
|
// write to settings file
|
|
41
|
-
await writeScanSettings(settings, params.
|
|
45
|
+
await writeScanSettings(settings, params.scan_settings_path);
|
|
42
46
|
}
|
|
43
|
-
export async function writeStartHeightToScanSettings(start_height,
|
|
44
|
-
const scanSettings = (await openScanSettingsFile(
|
|
47
|
+
export async function writeStartHeightToScanSettings(start_height, scan_settings_path = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
|
|
48
|
+
const scanSettings = (await openScanSettingsFile(scan_settings_path)) || {
|
|
45
49
|
node_url: LOCAL_NODE_DEFAULT_URL,
|
|
46
50
|
wallets: [],
|
|
47
51
|
start_height: null,
|
|
@@ -49,20 +53,20 @@ export async function writeStartHeightToScanSettings(start_height, settingsStore
|
|
|
49
53
|
scanSettings.start_height = start_height;
|
|
50
54
|
return await atomicWrite(SCAN_SETTINGS_STORE_NAME_DEFAULT, JSON.stringify(scanSettings, null, 2));
|
|
51
55
|
}
|
|
52
|
-
export async function writeDaemonHeightAsStartHeightToScanSettings(node_url,
|
|
56
|
+
export async function writeDaemonHeightAsStartHeightToScanSettings(node_url, scan_settings_path = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
|
|
53
57
|
const getInfo = await get_info(node_url);
|
|
54
|
-
const scanSettings = (await openScanSettingsFile(
|
|
58
|
+
const scanSettings = (await openScanSettingsFile(scan_settings_path)) || {
|
|
55
59
|
node_url: LOCAL_NODE_DEFAULT_URL,
|
|
56
60
|
wallets: [],
|
|
57
61
|
start_height: null,
|
|
58
62
|
};
|
|
59
63
|
scanSettings.start_height = getInfo.height - 1;
|
|
60
|
-
await atomicWrite(
|
|
64
|
+
await atomicWrite(scan_settings_path, JSON.stringify(scanSettings, null, 2));
|
|
61
65
|
return getInfo.height;
|
|
62
66
|
}
|
|
63
|
-
export async function cullTooLargeScanHeight(node_url,
|
|
67
|
+
export async function cullTooLargeScanHeight(node_url, scan_settings_path = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
|
|
64
68
|
const getInfo = await get_info(node_url);
|
|
65
|
-
const scanSettings = (await openScanSettingsFile(
|
|
69
|
+
const scanSettings = (await openScanSettingsFile(scan_settings_path)) || {
|
|
66
70
|
node_url: LOCAL_NODE_DEFAULT_URL,
|
|
67
71
|
wallets: [],
|
|
68
72
|
start_height: null,
|
|
@@ -70,21 +74,21 @@ export async function cullTooLargeScanHeight(node_url, settingsStorePath = SCAN_
|
|
|
70
74
|
if (scanSettings.start_height === null ||
|
|
71
75
|
scanSettings.start_height > getInfo.height - 1) {
|
|
72
76
|
scanSettings.start_height = getInfo.height - 1;
|
|
73
|
-
await atomicWrite(
|
|
77
|
+
await atomicWrite(scan_settings_path, JSON.stringify(scanSettings, null, 2));
|
|
74
78
|
}
|
|
75
79
|
return scanSettings.start_height;
|
|
76
80
|
}
|
|
77
|
-
export async function writeNodeUrlToScanSettings(node_url,
|
|
78
|
-
const scanSettings = (await openScanSettingsFile(
|
|
81
|
+
export async function writeNodeUrlToScanSettings(node_url, scan_settings_path = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
|
|
82
|
+
const scanSettings = (await openScanSettingsFile(scan_settings_path)) || {
|
|
79
83
|
node_url: LOCAL_NODE_DEFAULT_URL,
|
|
80
84
|
wallets: [],
|
|
81
85
|
start_height: null,
|
|
82
86
|
};
|
|
83
87
|
scanSettings.node_url = node_url;
|
|
84
|
-
return await atomicWrite(
|
|
88
|
+
return await atomicWrite(scan_settings_path, JSON.stringify(scanSettings, null, 2));
|
|
85
89
|
}
|
|
86
|
-
export async function readNodeUrlFromScanSettings(
|
|
87
|
-
const scanSettings = await openScanSettingsFile(
|
|
90
|
+
export async function readNodeUrlFromScanSettings(scan_settings_path = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
|
|
91
|
+
const scanSettings = await openScanSettingsFile(scan_settings_path);
|
|
88
92
|
return scanSettings?.node_url;
|
|
89
93
|
}
|
|
90
94
|
/**
|
|
@@ -98,18 +102,18 @@ export async function readNodeUrlFromScanSettings(settingsStorePath = SCAN_SETTI
|
|
|
98
102
|
*
|
|
99
103
|
* @param merchant_confirmations - Number of confirmations required before
|
|
100
104
|
* accepting a payment, or `null` to leave it unset.
|
|
101
|
-
* @param
|
|
105
|
+
* @param scan_settings_path - Optional path for the settings file.
|
|
102
106
|
* Defaults to `SCAN_SETTINGS_STORE_NAME_DEFAULT`.
|
|
103
107
|
* @returns A promise that resolves when the file is successfully written.
|
|
104
108
|
*/
|
|
105
|
-
export async function writeMerchantConfirmationsToScanSettings(merchant_confirmations,
|
|
106
|
-
const scanSettings = (await openScanSettingsFile(
|
|
109
|
+
export async function writeMerchantConfirmationsToScanSettings(merchant_confirmations, scan_settings_path = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
|
|
110
|
+
const scanSettings = (await openScanSettingsFile(scan_settings_path)) || {
|
|
107
111
|
node_url: LOCAL_NODE_DEFAULT_URL,
|
|
108
112
|
wallets: [],
|
|
109
113
|
start_height: null,
|
|
110
114
|
};
|
|
111
115
|
scanSettings.merchant_confirmations = merchant_confirmations;
|
|
112
|
-
return await atomicWrite(
|
|
116
|
+
return await atomicWrite(scan_settings_path, JSON.stringify(scanSettings, null, 2));
|
|
113
117
|
}
|
|
114
118
|
/**
|
|
115
119
|
* Reads the merchant confirmation threshold from scan settings.
|
|
@@ -121,13 +125,13 @@ export async function writeMerchantConfirmationsToScanSettings(merchant_confirma
|
|
|
121
125
|
* for a payment. The actual enforcement of this policy is left to the
|
|
122
126
|
* custom payment-system code that consumes this library.
|
|
123
127
|
*
|
|
124
|
-
* @param
|
|
128
|
+
* @param scan_settings_path - Optional path for the settings file.
|
|
125
129
|
* Defaults to `SCAN_SETTINGS_STORE_NAME_DEFAULT`.
|
|
126
130
|
* @returns The configured number of confirmations, null, or `undefined`
|
|
127
131
|
* if the setting has not been persisted yet.
|
|
128
132
|
*/
|
|
129
|
-
export async function readMerchantConfirmationsFromScanSettings(
|
|
130
|
-
const scanSettings = await openScanSettingsFile(
|
|
133
|
+
export async function readMerchantConfirmationsFromScanSettings(scan_settings_path = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
|
|
134
|
+
const scanSettings = await openScanSettingsFile(scan_settings_path);
|
|
131
135
|
return scanSettings?.merchant_confirmations;
|
|
132
136
|
}
|
|
133
137
|
/**
|
|
@@ -142,7 +146,7 @@ export async function readMerchantConfirmationsFromScanSettings(settingsStorePat
|
|
|
142
146
|
* }
|
|
143
147
|
* ```
|
|
144
148
|
*
|
|
145
|
-
* @param
|
|
149
|
+
* @param scan_settings_path - Path to the settings file. Defaults to `SCAN_SETTINGS_STORE_NAME_DEFAULT`.
|
|
146
150
|
* @returns The parsed {@link ScanSettings} object if file exists and is valid JSON, otherwise `undefined`.
|
|
147
151
|
*/
|
|
148
152
|
export async function readScanSettings(scan_settings_path = SCAN_SETTINGS_STORE_NAME_DEFAULT) {
|
|
@@ -231,7 +235,7 @@ export async function writeWalletToScanSettings(params) {
|
|
|
231
235
|
wallets: [
|
|
232
236
|
{
|
|
233
237
|
primary_address: params.primary_address,
|
|
234
|
-
subaddress_index: params.subaddress_index ||
|
|
238
|
+
subaddress_index: params.subaddress_index || SUB_ADDRESS_INDEX_DEFAULT_VALUE,
|
|
235
239
|
halted: params.halted,
|
|
236
240
|
wallet_route: params.wallet_route,
|
|
237
241
|
wallet_name: params.wallet_name,
|
|
@@ -261,7 +265,7 @@ export async function writeWalletToScanSettings(params) {
|
|
|
261
265
|
// wallet does not exist yet in settings
|
|
262
266
|
scanSettings.wallets.push({
|
|
263
267
|
primary_address: params.primary_address,
|
|
264
|
-
subaddress_index: params.subaddress_index ||
|
|
268
|
+
subaddress_index: params.subaddress_index || SUB_ADDRESS_INDEX_DEFAULT_VALUE,
|
|
265
269
|
halted: params.halted,
|
|
266
270
|
wallet_route: params.wallet_route,
|
|
267
271
|
wallet_name: params.wallet_name,
|
|
@@ -273,7 +277,9 @@ export async function writeWalletToScanSettings(params) {
|
|
|
273
277
|
const wallet = scanSettings.wallets[already_has_settings];
|
|
274
278
|
if (wallet) {
|
|
275
279
|
wallet.subaddress_index =
|
|
276
|
-
params.subaddress_index ||
|
|
280
|
+
params.subaddress_index ||
|
|
281
|
+
wallet.subaddress_index ||
|
|
282
|
+
SUB_ADDRESS_INDEX_DEFAULT_VALUE;
|
|
277
283
|
wallet.halted = params.halted || wallet.halted;
|
|
278
284
|
wallet.wallet_route = params.wallet_route || wallet.wallet_route;
|
|
279
285
|
wallet.wallet_name = params.wallet_name || wallet.wallet_name;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { Output } from "../../api";
|
|
2
|
+
import { WasmProcessor } from "../../wasm-processing/wasmProcessor";
|
|
2
3
|
export type KeyImage = string;
|
|
3
|
-
export declare function computeKeyImage(output: Output, sender_spend_key: string): Promise<KeyImage | undefined>;
|
|
4
|
+
export declare function computeKeyImage(output: Output, sender_spend_key: string, wasmProcessor?: WasmProcessor): Promise<KeyImage | undefined>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { monero_wallet_api_wasm } from "../../wasm-processing/wasmFile";
|
|
1
2
|
import { WasmProcessor } from "../../wasm-processing/wasmProcessor";
|
|
2
|
-
export async function computeKeyImage(output, sender_spend_key) {
|
|
3
|
-
|
|
3
|
+
export async function computeKeyImage(output, sender_spend_key, wasmProcessor) {
|
|
4
|
+
if (!wasmProcessor)
|
|
5
|
+
wasmProcessor = await WasmProcessor.init(monero_wallet_api_wasm);
|
|
4
6
|
wasmProcessor.writeToWasmMemory = (ptr, len) => {
|
|
5
7
|
wasmProcessor.writeString(ptr, len, output.serialized);
|
|
6
8
|
wasmProcessor.writeToWasmMemory = (ptr, len) => {
|
|
@@ -46,6 +46,7 @@ export declare class ScanCacheOpened {
|
|
|
46
46
|
readonly decoyRetrySizes: number[];
|
|
47
47
|
static create(params: ScanCacheOpenedCreateParams): Promise<ScanCacheOpened>;
|
|
48
48
|
get start_height(): number | null;
|
|
49
|
+
get subaddress_index(): number;
|
|
49
50
|
get current_height(): number | null;
|
|
50
51
|
get current_top_range_height(): number | null;
|
|
51
52
|
changeStartHeight(start_height: number | null): Promise<void>;
|
|
@@ -60,6 +61,7 @@ export declare class ScanCacheOpened {
|
|
|
60
61
|
retry(): Promise<boolean>;
|
|
61
62
|
sendTransaction(signedTx: string): Promise<SendRawTransactionResult>;
|
|
62
63
|
signTransaction(unsignedTx: string): Promise<string>;
|
|
64
|
+
getFeeEstimate(): Promise<FeeEstimateResponse>;
|
|
63
65
|
calculateFeeAndSelectInputs(params: CreateTransactionParams): Promise<{
|
|
64
66
|
selectedInputs: Output[];
|
|
65
67
|
feeEstimate: {
|
|
@@ -70,10 +72,16 @@ export declare class ScanCacheOpened {
|
|
|
70
72
|
};
|
|
71
73
|
}>;
|
|
72
74
|
makeTransactionFromSelectedInputs(payments: Payment[], selectedInputs: Output[], feeEstimate: FeeEstimateResponse): Promise<string>;
|
|
75
|
+
makeSweepTransactionFromSelectedInputs(destination_address: string, selectedInputs: Output[], feeEstimate: FeeEstimateResponse): Promise<string>;
|
|
73
76
|
/**
|
|
74
77
|
* this function returns the unsigned transaction, throws {@link SendError}
|
|
75
78
|
*/
|
|
76
79
|
makeTransaction(params: CreateTransactionParams): Promise<string>;
|
|
80
|
+
/**
|
|
81
|
+
* sweep inputs to external wallet address (the wallet will receive input amount - fee)
|
|
82
|
+
* @param inputs use spendableInputs() to find inputs to put in
|
|
83
|
+
*/
|
|
84
|
+
sweepToExternalWallet(destination_address: string, inputs: Output[]): Promise<string>;
|
|
77
85
|
get daemon_height(): number;
|
|
78
86
|
get amount(): bigint;
|
|
79
87
|
get pending_amount(): bigint;
|
|
@@ -134,6 +142,9 @@ export declare class ScanCacheOpened {
|
|
|
134
142
|
* for all primary addresses
|
|
135
143
|
*/
|
|
136
144
|
feed(params: CacheChangedCallbackParameters): Promise<void>;
|
|
145
|
+
private _highest_subaddress_index;
|
|
146
|
+
private _no_stats;
|
|
147
|
+
get no_stats(): boolean;
|
|
137
148
|
private _cache;
|
|
138
149
|
private worker?;
|
|
139
150
|
private last_eta_height;
|
|
@@ -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) {
|