@spirobel/monero-wallet-api 0.5.1 → 0.5.3
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/dist/actionlog/actionLog.d.ts +4 -0
- package/dist/actionlog/actionLog.js +9 -0
- package/dist/actionlog/actionLogOpened.d.ts +1 -0
- package/dist/actionlog/actionLogOpened.js +1 -0
- package/dist/api.d.ts +3 -2
- package/dist/api.js +3 -2
- package/dist/scanning-syncing/backgroundWorker.js +1 -1
- package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.js +29 -3
- package/dist/scanning-syncing/blocksbuffer/blocksbufferCoordination.js +6 -3
- package/dist/scanning-syncing/connectionStatus.d.ts +1 -4
- package/dist/scanning-syncing/connectionStatus.js +1 -31
- package/dist/scanning-syncing/connectionStatusOpened.d.ts +15 -0
- package/dist/scanning-syncing/connectionStatusOpened.js +51 -0
- package/dist/scanning-syncing/openWallet.js +1 -1
- package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +55 -7
- package/dist/scanning-syncing/scanresult/scanCacheOpened.js +191 -50
- package/dist/scanning-syncing/scanresult/scanCoordination.d.ts +1 -1
- package/dist/scanning-syncing/scanresult/scanCoordination.js +64 -2
- package/dist/scanning-syncing/scansettings/scanSettings.d.ts +165 -0
- package/dist/scanning-syncing/scansettings/scanSettings.js +338 -0
- package/dist/scanning-syncing/worker-entrypoints/worker.js +1 -1
- package/dist/scanning-syncing/worker-mains/worker.js +1 -2
- package/dist/scansettings/scanSettings.d.ts +165 -0
- package/dist/scansettings/scanSettings.js +338 -0
- package/dist/scansettings/scanSettingsOpened.d.ts +127 -0
- package/dist/scansettings/scanSettingsOpened.js +475 -0
- package/dist/viewpair/ViewPair.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import { LOCAL_NODE_DEFAULT_URL } from "../node-interaction/nodeUrl";
|
|
2
|
+
import { LOGGING_FUNCTIONS, } from "../io/logging";
|
|
3
|
+
import { getPathPrefix, makeSpendKeyFromSeed, makeViewKey, openScanSettingsFile, SCAN_SETTINGS_STORE_NAME_DEFAULT, SUB_ADDRESS_INDEX_DEFAULT_VALUE, walletSettingsPlusKeys, writeEnvLineToDotEnvRefresh, writeScanSettings, writeViewKeyToDotEnv, } from "../api";
|
|
4
|
+
export class ScanSettingsOpened {
|
|
5
|
+
_scan_settings_path;
|
|
6
|
+
_pathPrefix;
|
|
7
|
+
/**
|
|
8
|
+
* open the settings file and return a new ScanSettingsOpened instance.
|
|
9
|
+
* if the file does not exist, creates one with defaults.
|
|
10
|
+
*/
|
|
11
|
+
static async create(scan_settings_path = SCAN_SETTINGS_STORE_NAME_DEFAULT, pathPrefix) {
|
|
12
|
+
pathPrefix = getPathPrefix(scan_settings_path, pathPrefix);
|
|
13
|
+
let settings = await openScanSettingsFile(scan_settings_path);
|
|
14
|
+
if (!settings) {
|
|
15
|
+
settings = {
|
|
16
|
+
wallets: [],
|
|
17
|
+
node_url: LOCAL_NODE_DEFAULT_URL,
|
|
18
|
+
start_height: null,
|
|
19
|
+
};
|
|
20
|
+
// persist the defaults so the file exists on disk
|
|
21
|
+
await writeScanSettings(settings, scan_settings_path);
|
|
22
|
+
}
|
|
23
|
+
ScanSettingsOpened._validateLogOptions(settings.logs_include, settings.logs_exclude);
|
|
24
|
+
return new ScanSettingsOpened(settings, scan_settings_path, pathPrefix);
|
|
25
|
+
}
|
|
26
|
+
get node_url() {
|
|
27
|
+
return this._settings.node_url;
|
|
28
|
+
}
|
|
29
|
+
get start_height() {
|
|
30
|
+
return this._settings.start_height;
|
|
31
|
+
}
|
|
32
|
+
get merchant_confirmations() {
|
|
33
|
+
return this._settings.merchant_confirmations;
|
|
34
|
+
}
|
|
35
|
+
get cpu_worker_count() {
|
|
36
|
+
return this._settings.cpu_worker_count;
|
|
37
|
+
}
|
|
38
|
+
get logs() {
|
|
39
|
+
return this._settings.logs;
|
|
40
|
+
}
|
|
41
|
+
get logs_include() {
|
|
42
|
+
return this._settings.logs_include;
|
|
43
|
+
}
|
|
44
|
+
get logs_exclude() {
|
|
45
|
+
return this._settings.logs_exclude;
|
|
46
|
+
}
|
|
47
|
+
get wallets() {
|
|
48
|
+
return this._settings.wallets;
|
|
49
|
+
}
|
|
50
|
+
get scan_settings_path() {
|
|
51
|
+
return this._scan_settings_path;
|
|
52
|
+
}
|
|
53
|
+
get pathPrefix() {
|
|
54
|
+
return this._pathPrefix;
|
|
55
|
+
}
|
|
56
|
+
async setNodeUrl(node_url) {
|
|
57
|
+
await this.reload();
|
|
58
|
+
this._settings.node_url = ScanSettingsOpened._validateNodeUrl(node_url);
|
|
59
|
+
await this._persist();
|
|
60
|
+
}
|
|
61
|
+
async setStartHeight(start_height) {
|
|
62
|
+
await this.reload();
|
|
63
|
+
ScanSettingsOpened._validateStartHeight(start_height);
|
|
64
|
+
this._settings.start_height = start_height;
|
|
65
|
+
await this._persist();
|
|
66
|
+
}
|
|
67
|
+
async setMerchantConfirmations(merchant_confirmations) {
|
|
68
|
+
await this.reload();
|
|
69
|
+
if (merchant_confirmations === undefined ||
|
|
70
|
+
merchant_confirmations === null) {
|
|
71
|
+
delete this._settings.merchant_confirmations;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
ScanSettingsOpened._validateMerchantConfirmations(merchant_confirmations);
|
|
75
|
+
this._settings.merchant_confirmations = merchant_confirmations;
|
|
76
|
+
}
|
|
77
|
+
await this._persist();
|
|
78
|
+
}
|
|
79
|
+
async setCpuWorkerCount(cpu_worker_count) {
|
|
80
|
+
await this.reload();
|
|
81
|
+
if (cpu_worker_count === null || cpu_worker_count === undefined) {
|
|
82
|
+
delete this._settings.cpu_worker_count;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
ScanSettingsOpened._validateCpuWorkerCount(cpu_worker_count);
|
|
86
|
+
this._settings.cpu_worker_count = cpu_worker_count;
|
|
87
|
+
}
|
|
88
|
+
await this._persist();
|
|
89
|
+
}
|
|
90
|
+
async setLogSettings(logs, logs_include, logs_exclude) {
|
|
91
|
+
await this.reload();
|
|
92
|
+
if (logs === null) {
|
|
93
|
+
delete this._settings.logs;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
this._settings.logs = logs;
|
|
97
|
+
}
|
|
98
|
+
if (logs_include === null) {
|
|
99
|
+
delete this._settings.logs_include;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this._settings.logs_include = logs_include;
|
|
103
|
+
}
|
|
104
|
+
if (logs_exclude === null) {
|
|
105
|
+
delete this._settings.logs_exclude;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
this._settings.logs_exclude = logs_exclude;
|
|
109
|
+
}
|
|
110
|
+
// validate before persisting
|
|
111
|
+
ScanSettingsOpened._validateLogOptions(this._settings.logs_include, this._settings.logs_exclude);
|
|
112
|
+
await this._persist();
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* get a wallet's settings (without env keys).
|
|
116
|
+
* returns undefined if not found.
|
|
117
|
+
*/
|
|
118
|
+
getWallet(primary_address) {
|
|
119
|
+
return this._settings.wallets.find((w) => w.primary_address === primary_address);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* get a wallet's settings merged with env keys.
|
|
123
|
+
* throws if not found or view key missing.
|
|
124
|
+
*/
|
|
125
|
+
async getWalletOpened(primary_address) {
|
|
126
|
+
const wallet = this.getWallet(primary_address);
|
|
127
|
+
if (!wallet)
|
|
128
|
+
throw new Error(`wallet not found: ${primary_address} in ${this._scan_settings_path}`);
|
|
129
|
+
return await walletSettingsPlusKeys({
|
|
130
|
+
...wallet,
|
|
131
|
+
node_url: this._settings.node_url,
|
|
132
|
+
start_height: this._settings.start_height,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* get all wallets merged with env keys.
|
|
137
|
+
* wallets missing env keys are skipped.
|
|
138
|
+
*/
|
|
139
|
+
async getWalletsOpened() {
|
|
140
|
+
const opened = [];
|
|
141
|
+
for (const wallet of this._settings.wallets) {
|
|
142
|
+
try {
|
|
143
|
+
const withKeys = await walletSettingsPlusKeys({
|
|
144
|
+
...wallet,
|
|
145
|
+
node_url: this._settings.node_url,
|
|
146
|
+
start_height: this._settings.start_height,
|
|
147
|
+
});
|
|
148
|
+
opened.push(withKeys);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// wallet has no view key in env, skip
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return opened;
|
|
155
|
+
}
|
|
156
|
+
walletExists(primary_address) {
|
|
157
|
+
return this._settings.wallets.some((w) => w.primary_address === primary_address);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* add a new view wallet to settings and write its view key to env.
|
|
161
|
+
* if the wallet already exists, updates it instead.
|
|
162
|
+
*/
|
|
163
|
+
async addViewWallet(primary_address, view_key, fields) {
|
|
164
|
+
primary_address = primary_address.trim();
|
|
165
|
+
view_key = view_key.trim();
|
|
166
|
+
ScanSettingsOpened._validateAddress(primary_address);
|
|
167
|
+
ScanSettingsOpened._validateViewKey(view_key);
|
|
168
|
+
ScanSettingsOpened._validateWalletName(fields?.wallet_name);
|
|
169
|
+
ScanSettingsOpened._validateWalletSlot(fields?.wallet_slot);
|
|
170
|
+
ScanSettingsOpened._validateWalletRoute(fields?.wallet_route);
|
|
171
|
+
ScanSettingsOpened._validateSubaddressIndex(fields?.subaddress_index);
|
|
172
|
+
ScanSettingsOpened._validateHalted(fields?.halted);
|
|
173
|
+
await this.reload();
|
|
174
|
+
const existing = this.getWallet(primary_address);
|
|
175
|
+
if (existing) {
|
|
176
|
+
throw new Error(`wallet already exists: ${primary_address} in ${this._scan_settings_path}`);
|
|
177
|
+
}
|
|
178
|
+
// write view key to env first
|
|
179
|
+
await writeViewKeyToDotEnv(primary_address, view_key);
|
|
180
|
+
const subaddress_index = fields?.subaddress_index ?? SUB_ADDRESS_INDEX_DEFAULT_VALUE;
|
|
181
|
+
this._settings.wallets.push({
|
|
182
|
+
primary_address,
|
|
183
|
+
...fields,
|
|
184
|
+
subaddress_index,
|
|
185
|
+
});
|
|
186
|
+
await this._persist();
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* add a new view wallet to settings and write its view key to env.
|
|
190
|
+
* if the wallet already exists, updates it instead.
|
|
191
|
+
*/
|
|
192
|
+
async addSpendWallet(wallet_secret, fields) {
|
|
193
|
+
// first part of writeWalletSecretsToDotEnv()
|
|
194
|
+
const spend_key = await makeSpendKeyFromSeed(wallet_secret.toHex());
|
|
195
|
+
const view_pair = await makeViewKey(spend_key);
|
|
196
|
+
const primary_address = view_pair.mainnet_primary;
|
|
197
|
+
ScanSettingsOpened._validateWalletName(fields?.wallet_name);
|
|
198
|
+
ScanSettingsOpened._validateWalletSlot(fields?.wallet_slot);
|
|
199
|
+
ScanSettingsOpened._validateWalletRoute(fields?.wallet_route);
|
|
200
|
+
ScanSettingsOpened._validateSubaddressIndex(fields?.subaddress_index);
|
|
201
|
+
ScanSettingsOpened._validateHalted(fields?.halted);
|
|
202
|
+
await this.reload();
|
|
203
|
+
const existing = this.getWallet(primary_address);
|
|
204
|
+
if (existing) {
|
|
205
|
+
throw new Error(`wallet already exists: ${primary_address} in ${this._scan_settings_path}`);
|
|
206
|
+
}
|
|
207
|
+
// second part of writeWalletSecretsToDotEnv(),
|
|
208
|
+
await writeEnvLineToDotEnvRefresh(`vk${primary_address}`, view_pair.view_key);
|
|
209
|
+
await writeEnvLineToDotEnvRefresh(`sk${primary_address}`, spend_key);
|
|
210
|
+
const subaddress_index = fields?.subaddress_index ?? SUB_ADDRESS_INDEX_DEFAULT_VALUE;
|
|
211
|
+
this._settings.wallets.push({
|
|
212
|
+
primary_address,
|
|
213
|
+
...fields,
|
|
214
|
+
subaddress_index,
|
|
215
|
+
});
|
|
216
|
+
await this._persist();
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* remove a wallet from settings by primary address.
|
|
220
|
+
* does not remove the view key from env.
|
|
221
|
+
*/
|
|
222
|
+
async removeWallet(primary_address) {
|
|
223
|
+
await this.reload();
|
|
224
|
+
this._settings.wallets = this._settings.wallets.filter((w) => w.primary_address !== primary_address);
|
|
225
|
+
await this._persist();
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* update specific fields on an existing wallet.
|
|
229
|
+
* set a field to null to unset it.
|
|
230
|
+
*/
|
|
231
|
+
async updateWallet(primary_address, fields) {
|
|
232
|
+
await this.reload();
|
|
233
|
+
const wallet = this.getWallet(primary_address);
|
|
234
|
+
if (!wallet)
|
|
235
|
+
throw new Error(`wallet not found: ${primary_address} in ${this._scan_settings_path}`);
|
|
236
|
+
ScanSettingsOpened._validateWalletName(fields.wallet_name);
|
|
237
|
+
ScanSettingsOpened._validateWalletSlot(fields.wallet_slot);
|
|
238
|
+
ScanSettingsOpened._validateSubaddressIndex(fields.subaddress_index);
|
|
239
|
+
ScanSettingsOpened._validateWalletRoute(fields.wallet_route);
|
|
240
|
+
ScanSettingsOpened._validateHalted(fields.halted);
|
|
241
|
+
wallet.wallet_name = fields.wallet_name ?? wallet.wallet_name;
|
|
242
|
+
wallet.wallet_slot = fields.wallet_slot ?? wallet.wallet_slot;
|
|
243
|
+
wallet.wallet_route = fields.wallet_route ?? wallet.wallet_route;
|
|
244
|
+
wallet.subaddress_index =
|
|
245
|
+
fields.subaddress_index ?? wallet.subaddress_index;
|
|
246
|
+
wallet.halted = fields.halted ?? wallet.halted;
|
|
247
|
+
if (fields.halted === null)
|
|
248
|
+
delete wallet.halted;
|
|
249
|
+
if (fields.subaddress_index === null)
|
|
250
|
+
delete wallet.subaddress_index;
|
|
251
|
+
if (fields.wallet_route === null)
|
|
252
|
+
delete wallet.wallet_route;
|
|
253
|
+
if (fields.wallet_slot === null)
|
|
254
|
+
delete wallet.wallet_slot;
|
|
255
|
+
if (fields.wallet_name === null)
|
|
256
|
+
delete wallet.wallet_name;
|
|
257
|
+
await this._persist();
|
|
258
|
+
}
|
|
259
|
+
async haltWallet(primary_address) {
|
|
260
|
+
await this.updateWallet(primary_address, { halted: true });
|
|
261
|
+
}
|
|
262
|
+
async unhaltWallet(primary_address) {
|
|
263
|
+
await this.updateWallet(primary_address, { halted: false });
|
|
264
|
+
}
|
|
265
|
+
async setWalletName(primary_address, name) {
|
|
266
|
+
await this.updateWallet(primary_address, { wallet_name: name ?? null });
|
|
267
|
+
}
|
|
268
|
+
async setWalletSlot(primary_address, slot) {
|
|
269
|
+
await this.updateWallet(primary_address, { wallet_slot: slot ?? null });
|
|
270
|
+
}
|
|
271
|
+
async setWalletRoute(primary_address, route) {
|
|
272
|
+
await this.updateWallet(primary_address, { wallet_route: route ?? null });
|
|
273
|
+
}
|
|
274
|
+
async setSubaddressIndex(primary_address, index) {
|
|
275
|
+
await this.updateWallet(primary_address, {
|
|
276
|
+
subaddress_index: index ?? null,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* reread the settings file from disk and replace inmemory state.
|
|
281
|
+
*/
|
|
282
|
+
async reload() {
|
|
283
|
+
const settings = await openScanSettingsFile(this._scan_settings_path);
|
|
284
|
+
if (settings) {
|
|
285
|
+
ScanSettingsOpened._validateLogOptions(settings.logs_include, settings.logs_exclude);
|
|
286
|
+
this._settings = settings;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* validate that all entries in logs_include and logs_exclude
|
|
291
|
+
* are valid function names from LOGGING_FUNCTIONS.
|
|
292
|
+
* throws if any entry is invalid.
|
|
293
|
+
*/
|
|
294
|
+
static _validateLogOptions(logs_include, logs_exclude) {
|
|
295
|
+
const valid = new Set(LOGGING_FUNCTIONS);
|
|
296
|
+
const invalid = [];
|
|
297
|
+
for (const arr of [logs_include, logs_exclude]) {
|
|
298
|
+
if (!arr)
|
|
299
|
+
continue;
|
|
300
|
+
for (const item of arr) {
|
|
301
|
+
if (!valid.has(item)) {
|
|
302
|
+
invalid.push(item);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (invalid.length > 0) {
|
|
307
|
+
throw new Error(`invalid log function(s): ${invalid.join(", ")}. ` +
|
|
308
|
+
`valid options are: ${[...valid].join(", ")}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
static _validateAddress(address) {
|
|
312
|
+
if (!isValidMoneroAddress(address)) {
|
|
313
|
+
throw new Error(`invalid monero address: ${address}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
static _validateViewKey(key) {
|
|
317
|
+
if (!isValidMoneroPrivateKey(key)) {
|
|
318
|
+
throw new Error(`invalid secret view key: ${key}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
static _validateNodeUrl(url) {
|
|
322
|
+
let parsed;
|
|
323
|
+
try {
|
|
324
|
+
parsed = new URL(url);
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
throw new Error(`invalid node URL: ${url}`);
|
|
328
|
+
}
|
|
329
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
330
|
+
throw new Error(`unsupported protocol: ${parsed.protocol}`);
|
|
331
|
+
}
|
|
332
|
+
if (!parsed.hostname) {
|
|
333
|
+
throw new Error(`invalid node URL, missing hostname: ${url}`);
|
|
334
|
+
}
|
|
335
|
+
return url;
|
|
336
|
+
}
|
|
337
|
+
static _validateStartHeight(start_height) {
|
|
338
|
+
if (start_height !== null &&
|
|
339
|
+
(typeof start_height !== "number" || start_height < 0)) {
|
|
340
|
+
throw new Error(`invalid start height: ${start_height}. must be null or a number >= 0`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
static _validateCpuWorkerCount(cpu_worker_count) {
|
|
344
|
+
if (cpu_worker_count !== null &&
|
|
345
|
+
(typeof cpu_worker_count !== "number" || cpu_worker_count < 0)) {
|
|
346
|
+
throw new Error(`invalid cpu_worker_count: ${cpu_worker_count}. must be null or a number >= 0`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
static _validateMerchantConfirmations(merchant_confirmations) {
|
|
350
|
+
if (merchant_confirmations !== null &&
|
|
351
|
+
(typeof merchant_confirmations !== "number" || merchant_confirmations < 0)) {
|
|
352
|
+
throw new Error(`invalid merchant_confirmations: ${merchant_confirmations}. must be null or a number >= 0`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
static _validateWalletName(name) {
|
|
356
|
+
if (name === undefined || name === null)
|
|
357
|
+
return;
|
|
358
|
+
if (name.length > 100) {
|
|
359
|
+
throw new Error(`wallet name must be 100 characters or less, got ${name.length}`);
|
|
360
|
+
}
|
|
361
|
+
if (!/^[a-zA-Z0-9 _.-]+$/.test(name)) {
|
|
362
|
+
throw new Error(`wallet name must be alphanumeric with spaces, hyphens, underscores, or dots, got: ${name}`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
static _validateWalletSlot(slot) {
|
|
366
|
+
if (slot === undefined || slot === null)
|
|
367
|
+
return;
|
|
368
|
+
if (typeof slot !== "number" || slot < 0 || !Number.isInteger(slot)) {
|
|
369
|
+
throw new Error(`wallet slot must be a number >= 0, got: ${slot}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
static _validateSubaddressIndex(index) {
|
|
373
|
+
if (index === undefined || index === null)
|
|
374
|
+
return;
|
|
375
|
+
if (typeof index !== "number" || index < 0 || !Number.isInteger(index)) {
|
|
376
|
+
throw new Error(`subaddress index must be a number >= 0, got: ${index}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
static _validateWalletRoute(route) {
|
|
380
|
+
if (route === undefined || route === null)
|
|
381
|
+
return;
|
|
382
|
+
const result = walletRouteFromString(route);
|
|
383
|
+
if (!result.ok) {
|
|
384
|
+
throw new Error(result.error);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
static _validateHalted(halted) {
|
|
388
|
+
if (halted === undefined || halted === null)
|
|
389
|
+
return;
|
|
390
|
+
if (typeof halted !== "boolean") {
|
|
391
|
+
throw new Error(`halted must be a boolean, got: ${halted}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
_settings;
|
|
395
|
+
constructor(settings, _scan_settings_path, _pathPrefix) {
|
|
396
|
+
this._scan_settings_path = _scan_settings_path;
|
|
397
|
+
this._pathPrefix = _pathPrefix;
|
|
398
|
+
this._settings = settings;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* write current in memory state to disk.
|
|
402
|
+
*/
|
|
403
|
+
async _persist() {
|
|
404
|
+
await writeScanSettings(this._settings, this._scan_settings_path);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function isValidMoneroAddress(address) {
|
|
408
|
+
// basic sanity check
|
|
409
|
+
return (address.length >= 95 && address.length <= 106 && isAlphaNumeric(address));
|
|
410
|
+
}
|
|
411
|
+
function isValidMoneroPrivateKey(key) {
|
|
412
|
+
return key.length === 64 && isAlphaNumeric(key);
|
|
413
|
+
}
|
|
414
|
+
function isAlphaNumeric(str) {
|
|
415
|
+
return str.match(/^[a-z0-9]+$/i) !== null;
|
|
416
|
+
}
|
|
417
|
+
// strictly speaking according to the RFC 1035
|
|
418
|
+
// there is a limit of 63 chars per label
|
|
419
|
+
// and other rules like no leading or trailing hyphens, no dots in a row
|
|
420
|
+
function isValidDomainName(str) {
|
|
421
|
+
if (!str || str.length === 0 || str.length > 253) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
return str.match(/^[a-z0-9.\-_]+$/i) !== null;
|
|
425
|
+
}
|
|
426
|
+
// copied from seedphrase package to not introduce a dependency
|
|
427
|
+
export function walletRouteFromString(input) {
|
|
428
|
+
const parts = input.split("/");
|
|
429
|
+
if (parts.length < 1 || !parts[0]) {
|
|
430
|
+
return { ok: false, error: "missing identity" };
|
|
431
|
+
}
|
|
432
|
+
if (parts.length < 2 || !parts[1]) {
|
|
433
|
+
return { ok: false, error: "missing domain" };
|
|
434
|
+
}
|
|
435
|
+
if (parts.length < 3 || !parts[2]) {
|
|
436
|
+
return { ok: false, error: "missing wallet_type" };
|
|
437
|
+
}
|
|
438
|
+
if (parts.length < 4 || !parts[3]) {
|
|
439
|
+
return { ok: false, error: "missing wallet_slot" };
|
|
440
|
+
}
|
|
441
|
+
if (parts.length > 4) {
|
|
442
|
+
return {
|
|
443
|
+
ok: false,
|
|
444
|
+
error: "wallet route should only have 4 parts separated by /",
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
const [identity, domain, wallet_type, wallet_slot] = parts;
|
|
448
|
+
if (wallet_type !== "single" &&
|
|
449
|
+
wallet_type !== "sa_multi" &&
|
|
450
|
+
wallet_type !== "pl_multi") {
|
|
451
|
+
return { ok: false, error: `invalid wallet_type: "${wallet_type}"` };
|
|
452
|
+
}
|
|
453
|
+
if (Number.isNaN(parseInt(wallet_slot))) {
|
|
454
|
+
return { ok: false, error: `invalid wallet_slot: "${wallet_slot}"` };
|
|
455
|
+
}
|
|
456
|
+
if (parseInt(wallet_slot) < 0) {
|
|
457
|
+
return { ok: false, error: `invalid wallet_slot: "${wallet_slot} < 0"` };
|
|
458
|
+
}
|
|
459
|
+
if (!isAlphaNumeric(identity)) {
|
|
460
|
+
return {
|
|
461
|
+
ok: false,
|
|
462
|
+
error: `invalid identity, not alpha numeric: "${identity}"`,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
if (!isValidDomainName(domain)) {
|
|
466
|
+
return {
|
|
467
|
+
ok: false,
|
|
468
|
+
error: `invalid domain, : "${domain}"`,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
return {
|
|
472
|
+
ok: true,
|
|
473
|
+
route: { identity, domain, wallet_type, wallet_slot },
|
|
474
|
+
};
|
|
475
|
+
}
|
|
@@ -8,7 +8,7 @@ import { monero_wallet_api_wasm } from "../wasm-processing/wasmFile";
|
|
|
8
8
|
import { WasmProcessor } from "../wasm-processing/wasmProcessor";
|
|
9
9
|
import { LOCAL_NODE_DEFAULT_URL, } from "../node-interaction/nodeUrl";
|
|
10
10
|
import { get_block_headers_range, get_info, } from "../api";
|
|
11
|
-
import { readWalletFromScanSettings } from "../
|
|
11
|
+
import { readWalletFromScanSettings } from "../api";
|
|
12
12
|
import { sleep } from "../io/sleep";
|
|
13
13
|
/**
|
|
14
14
|
* This class is useful to interact with Moneros DaemonRpc binary requests in a convenient way.
|