@spirobel/monero-wallet-api 0.6.4 → 0.6.5
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/scanning-syncing/scanresult/scanCacheOpened.d.ts +3 -1
- package/dist/scanning-syncing/scanresult/scanCacheOpened.js +130 -104
- package/dist/scanning-syncing/scanresult/scanResult.js +3 -1
- package/dist/scanning-syncing/scanresult/scanStats.d.ts +1 -0
- package/dist/scanning-syncing/scanresult/scanStats.js +20 -4
- package/package.json +1 -1
|
@@ -102,6 +102,9 @@ export declare class ScanCacheOpened {
|
|
|
102
102
|
get pending_amount(): bigint;
|
|
103
103
|
get subaddresses(): Subaddress[];
|
|
104
104
|
get tx_logs(): TxLog[];
|
|
105
|
+
private writeTxCache;
|
|
106
|
+
private beginInFlightSend;
|
|
107
|
+
private finishInFlightSend;
|
|
105
108
|
makeSignSendTransaction(params: CreateTransactionParams): Promise<SendRawTransactionResult>;
|
|
106
109
|
private outputsForIndexes;
|
|
107
110
|
makeSignSend(params: {
|
|
@@ -118,7 +121,6 @@ export declare class ScanCacheOpened {
|
|
|
118
121
|
txlog: TxLog;
|
|
119
122
|
inputs: Output[];
|
|
120
123
|
}[];
|
|
121
|
-
private recordSend;
|
|
122
124
|
dismissSendPlate(): Promise<void>;
|
|
123
125
|
/**
|
|
124
126
|
* makeStandardTransaction
|
|
@@ -8,7 +8,7 @@ import { ConnectionStatusOpened } from "../connectionStatusOpened";
|
|
|
8
8
|
import { ActionLogOpened, } from "../../actionlog/actionLogOpened";
|
|
9
9
|
import { sendToBackground } from "../../tools/globals";
|
|
10
10
|
import { findRange, lastRange, readCacheFileDefaultLocation, writeCacheFileDefaultLocationThrows, } from "./scanCache";
|
|
11
|
-
import { alignScanStatsWithCache, isSelfSpent, processTxlogInputs, processTxlogPayments, writeStatsFileDefaultLocation, } from "./scanStats";
|
|
11
|
+
import { alignScanStatsWithCache, isSelfSpent, processTxlogInputs, processTxlogPayments, txLogHoldsFunds, writeStatsFileDefaultLocation, } from "./scanStats";
|
|
12
12
|
import { readWriteConnectionStatusFile, } from "../connectionStatus";
|
|
13
13
|
export class ScanCacheOpened {
|
|
14
14
|
_scanSettings;
|
|
@@ -109,9 +109,7 @@ export class ScanCacheOpened {
|
|
|
109
109
|
get prepending_txs() {
|
|
110
110
|
const txs = [];
|
|
111
111
|
for (const txlog of this._cache.tx_logs || []) {
|
|
112
|
-
if (!txlog ||
|
|
113
|
-
!txlog.sendResult ||
|
|
114
|
-
(txlog.sendResult && txlog.sendResult.status !== "OK"))
|
|
112
|
+
if (!txlog || !txLogHoldsFunds(txlog))
|
|
115
113
|
continue;
|
|
116
114
|
const { inputSum, alreadyRecognizedAsSpend } = processTxlogInputs(txlog, this._cache);
|
|
117
115
|
if (alreadyRecognizedAsSpend)
|
|
@@ -392,15 +390,91 @@ export class ScanCacheOpened {
|
|
|
392
390
|
get tx_logs() {
|
|
393
391
|
return this._cache.tx_logs || [];
|
|
394
392
|
}
|
|
393
|
+
async writeTxCache(writeCallback, changed_outputs) {
|
|
394
|
+
await writeCacheFileDefaultLocationThrows({
|
|
395
|
+
primary_address: this.primary_address,
|
|
396
|
+
pathPrefix: this.pathPrefix,
|
|
397
|
+
writeCallback,
|
|
398
|
+
});
|
|
399
|
+
const newCache = await readCacheFileDefaultLocation(this.primary_address, this.pathPrefix);
|
|
400
|
+
if (!newCache)
|
|
401
|
+
throw new Error(`cache not found for primary address: ${this.primary_address}, and path prefix: ${this.pathPrefix}`);
|
|
402
|
+
await this.feed({ newCache, changed_outputs });
|
|
403
|
+
}
|
|
404
|
+
// stop the worker, lock the inputs, then let stats show pending.
|
|
405
|
+
async beginInFlightSend(args) {
|
|
406
|
+
await this.stopWorker();
|
|
407
|
+
const inputs_index = args.inputs.map((input) => String(input.index_on_blockchain));
|
|
408
|
+
let txLogIndex = 0;
|
|
409
|
+
try {
|
|
410
|
+
await this.writeTxCache(async (cache) => {
|
|
411
|
+
if (!cache.tx_logs)
|
|
412
|
+
cache.tx_logs = [];
|
|
413
|
+
if (!cache.pending_spent_utxos)
|
|
414
|
+
cache.pending_spent_utxos = {};
|
|
415
|
+
const txLog = {
|
|
416
|
+
feeEstimate: args.feeEstimate,
|
|
417
|
+
payments: args.payments,
|
|
418
|
+
node_url: this.node_url,
|
|
419
|
+
inputs_index,
|
|
420
|
+
height: this.current_height,
|
|
421
|
+
timestamp: Date.now(),
|
|
422
|
+
invocationId: args.invocationId,
|
|
423
|
+
};
|
|
424
|
+
txLogIndex = cache.tx_logs.push(txLog) - 1;
|
|
425
|
+
for (const inputId of inputs_index)
|
|
426
|
+
cache.pending_spent_utxos[inputId] = txLogIndex;
|
|
427
|
+
}, args.inputs.map((output) => ({ change_reason: "spent", output })));
|
|
428
|
+
}
|
|
429
|
+
catch (e) {
|
|
430
|
+
await this.unpause();
|
|
431
|
+
throw e;
|
|
432
|
+
}
|
|
433
|
+
return txLogIndex;
|
|
434
|
+
}
|
|
435
|
+
// update the same row with the node answer. unlock the inputs on failure.
|
|
436
|
+
async finishInFlightSend(args) {
|
|
437
|
+
const ok = args.sendResult?.status === "OK" && args.error === undefined;
|
|
438
|
+
try {
|
|
439
|
+
await this.writeTxCache(async (cache) => {
|
|
440
|
+
const row = cache.tx_logs?.[args.txLogIndex];
|
|
441
|
+
if (!row)
|
|
442
|
+
throw new Error("tx log missing");
|
|
443
|
+
if (args.invocationId)
|
|
444
|
+
row.invocationId = args.invocationId;
|
|
445
|
+
if (args.signedTx)
|
|
446
|
+
row.signed_tx = args.signedTx;
|
|
447
|
+
if (args.sendResult)
|
|
448
|
+
row.sendResult = args.sendResult;
|
|
449
|
+
if (!ok) {
|
|
450
|
+
row.error = args.error || args.sendResult?.reason || "send failed";
|
|
451
|
+
for (const inputId of row.inputs_index) {
|
|
452
|
+
if (cache.pending_spent_utxos?.[inputId] === args.txLogIndex)
|
|
453
|
+
delete cache.pending_spent_utxos[inputId];
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}, []);
|
|
457
|
+
}
|
|
458
|
+
finally {
|
|
459
|
+
await this.unpause();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
395
462
|
async makeSignSendTransaction(params) {
|
|
396
463
|
let maybeInputs = [];
|
|
397
464
|
let maybeFeeEstimate;
|
|
398
465
|
let maybeSendResult;
|
|
399
466
|
let maybeSignedTx;
|
|
467
|
+
let txLogIndex;
|
|
400
468
|
try {
|
|
401
469
|
const { selectedInputs, feeEstimate } = await this.calculateFeeAndSelectInputs(params);
|
|
402
470
|
maybeInputs = selectedInputs;
|
|
403
471
|
maybeFeeEstimate = feeEstimate;
|
|
472
|
+
txLogIndex = await this.beginInFlightSend({
|
|
473
|
+
payments: params.payments,
|
|
474
|
+
inputs: selectedInputs,
|
|
475
|
+
feeEstimate,
|
|
476
|
+
invocationId: params.invocationId,
|
|
477
|
+
});
|
|
404
478
|
const unsignedTx = await this.makeTransactionFromSelectedInputs(params.payments, selectedInputs, feeEstimate);
|
|
405
479
|
const signedTx = await this.signTransaction(unsignedTx);
|
|
406
480
|
maybeSignedTx = signedTx;
|
|
@@ -408,52 +482,29 @@ export class ScanCacheOpened {
|
|
|
408
482
|
maybeSendResult = sendResult;
|
|
409
483
|
if (sendResult.status !== "OK")
|
|
410
484
|
throw new Error("send raw transaction rpc returned error");
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
pathPrefix: this.pathPrefix,
|
|
417
|
-
writeCallback: async (cache) => {
|
|
418
|
-
if (!cache.tx_logs)
|
|
419
|
-
cache.tx_logs = [];
|
|
420
|
-
if (!cache.pending_spent_utxos)
|
|
421
|
-
cache.pending_spent_utxos = {};
|
|
422
|
-
const inputs_index = selectedInputs.map((input) => String(input.index_on_blockchain));
|
|
423
|
-
const txLog = {
|
|
424
|
-
sendResult,
|
|
425
|
-
feeEstimate,
|
|
426
|
-
payments: params.payments,
|
|
427
|
-
node_url: this.node_url,
|
|
428
|
-
inputs_index,
|
|
429
|
-
height: this.current_height,
|
|
430
|
-
timestamp: Date.now(),
|
|
431
|
-
invocationId: params.invocationId,
|
|
432
|
-
signed_tx: signedTx,
|
|
433
|
-
};
|
|
434
|
-
const newLen = cache.tx_logs.push(txLog);
|
|
435
|
-
const txLogIndex = newLen - 1;
|
|
436
|
-
for (const inputId of inputs_index) {
|
|
437
|
-
cache.pending_spent_utxos[inputId] = txLogIndex;
|
|
438
|
-
}
|
|
439
|
-
},
|
|
440
|
-
});
|
|
441
|
-
const newCache = await readCacheFileDefaultLocation(this.primary_address, this.pathPrefix);
|
|
442
|
-
if (!newCache)
|
|
443
|
-
throw new Error(`cache not found for primary address: ${this.primary_address}, and path prefix: ${this.pathPrefix}`);
|
|
444
|
-
const changed_outputs = selectedInputs.map((input) => ({
|
|
445
|
-
change_reason: "spent",
|
|
446
|
-
output: input,
|
|
447
|
-
}));
|
|
448
|
-
await this.feed({
|
|
449
|
-
newCache,
|
|
450
|
-
changed_outputs,
|
|
485
|
+
await this.finishInFlightSend({
|
|
486
|
+
txLogIndex,
|
|
487
|
+
sendResult,
|
|
488
|
+
signedTx,
|
|
489
|
+
invocationId: params.invocationId,
|
|
451
490
|
});
|
|
452
|
-
// restart the worker
|
|
453
|
-
await this.unpause();
|
|
454
491
|
return sendResult;
|
|
455
492
|
}
|
|
456
493
|
catch (e) {
|
|
494
|
+
if (txLogIndex !== undefined && maybeSendResult?.status !== "OK") {
|
|
495
|
+
await this.finishInFlightSend({
|
|
496
|
+
txLogIndex,
|
|
497
|
+
sendResult: maybeSendResult,
|
|
498
|
+
signedTx: maybeSignedTx,
|
|
499
|
+
error: String(e || "unknown error"),
|
|
500
|
+
invocationId: params.invocationId,
|
|
501
|
+
});
|
|
502
|
+
throw e;
|
|
503
|
+
}
|
|
504
|
+
if (txLogIndex !== undefined) {
|
|
505
|
+
await this.unpause();
|
|
506
|
+
throw e;
|
|
507
|
+
}
|
|
457
508
|
// before writing the scan cache, we stop the worker to avoid a race
|
|
458
509
|
await this.stopWorker();
|
|
459
510
|
// write txlog error to cache
|
|
@@ -529,31 +580,41 @@ export class ScanCacheOpened {
|
|
|
529
580
|
if (!inputs.length)
|
|
530
581
|
throw new Error("no inputs selected");
|
|
531
582
|
const feeEstimate = await this.getFeeEstimate();
|
|
532
|
-
const unsignedTx = await this.makeSweepTransactionFromSelectedInputs(params.address, inputs, feeEstimate);
|
|
533
|
-
const signedTx = await this.signTransaction(unsignedTx);
|
|
534
583
|
const payments = [{ address: params.address, amount: "0" }];
|
|
535
|
-
const
|
|
536
|
-
if (sendResult.status !== "OK") {
|
|
537
|
-
await this.recordSend({
|
|
538
|
-
payments,
|
|
539
|
-
inputs,
|
|
540
|
-
signedTx,
|
|
541
|
-
feeEstimate,
|
|
542
|
-
sendResult,
|
|
543
|
-
error: "send raw transaction rpc returned error",
|
|
544
|
-
lockInputs: false,
|
|
545
|
-
});
|
|
546
|
-
throw new Error("send raw transaction rpc returned error");
|
|
547
|
-
}
|
|
548
|
-
await this.recordSend({
|
|
584
|
+
const txLogIndex = await this.beginInFlightSend({
|
|
549
585
|
payments,
|
|
550
586
|
inputs,
|
|
551
|
-
signedTx,
|
|
552
587
|
feeEstimate,
|
|
553
|
-
sendResult,
|
|
554
|
-
lockInputs: true,
|
|
555
588
|
});
|
|
556
|
-
|
|
589
|
+
let sendResult;
|
|
590
|
+
let signedTx;
|
|
591
|
+
try {
|
|
592
|
+
const unsignedTx = await this.makeSweepTransactionFromSelectedInputs(params.address, inputs, feeEstimate);
|
|
593
|
+
signedTx = await this.signTransaction(unsignedTx);
|
|
594
|
+
sendResult = await this.sendTransaction(signedTx);
|
|
595
|
+
if (sendResult.status !== "OK")
|
|
596
|
+
throw new Error("send raw transaction rpc returned error");
|
|
597
|
+
await this.finishInFlightSend({
|
|
598
|
+
txLogIndex,
|
|
599
|
+
sendResult,
|
|
600
|
+
signedTx,
|
|
601
|
+
});
|
|
602
|
+
return sendResult;
|
|
603
|
+
}
|
|
604
|
+
catch (e) {
|
|
605
|
+
if (sendResult?.status !== "OK") {
|
|
606
|
+
await this.finishInFlightSend({
|
|
607
|
+
txLogIndex,
|
|
608
|
+
sendResult,
|
|
609
|
+
signedTx,
|
|
610
|
+
error: e instanceof Error ? e.message : String(e),
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
await this.unpause();
|
|
615
|
+
}
|
|
616
|
+
throw e;
|
|
617
|
+
}
|
|
557
618
|
}
|
|
558
619
|
async rebroadcastTx(txLogIndex) {
|
|
559
620
|
const txlog = this._cache.tx_logs?.[txLogIndex];
|
|
@@ -603,6 +664,9 @@ export class ScanCacheOpened {
|
|
|
603
664
|
continue;
|
|
604
665
|
if (txlog.sendResult?.status === "OK")
|
|
605
666
|
continue;
|
|
667
|
+
// no node answer yet. this row is still in flight.
|
|
668
|
+
if (!txlog.sendResult && !txlog.error)
|
|
669
|
+
continue;
|
|
606
670
|
const inputs = txlog.inputs_index
|
|
607
671
|
.map((id) => this._cache.outputs[id])
|
|
608
672
|
.filter((output) => output);
|
|
@@ -613,44 +677,6 @@ export class ScanCacheOpened {
|
|
|
613
677
|
}
|
|
614
678
|
return rows;
|
|
615
679
|
}
|
|
616
|
-
async recordSend(args) {
|
|
617
|
-
await this.stopWorker();
|
|
618
|
-
await writeCacheFileDefaultLocationThrows({
|
|
619
|
-
primary_address: this.primary_address,
|
|
620
|
-
pathPrefix: this.pathPrefix,
|
|
621
|
-
writeCallback: async (cache) => {
|
|
622
|
-
if (!cache.tx_logs)
|
|
623
|
-
cache.tx_logs = [];
|
|
624
|
-
if (!cache.pending_spent_utxos)
|
|
625
|
-
cache.pending_spent_utxos = {};
|
|
626
|
-
const inputs_index = args.inputs.map((input) => String(input.index_on_blockchain));
|
|
627
|
-
const txLog = {
|
|
628
|
-
sendResult: args.sendResult,
|
|
629
|
-
error: args.error,
|
|
630
|
-
feeEstimate: args.feeEstimate,
|
|
631
|
-
payments: args.payments,
|
|
632
|
-
node_url: this.node_url,
|
|
633
|
-
inputs_index,
|
|
634
|
-
height: this.current_height,
|
|
635
|
-
timestamp: Date.now(),
|
|
636
|
-
signed_tx: args.signedTx,
|
|
637
|
-
};
|
|
638
|
-
const txLogIndex = cache.tx_logs.push(txLog) - 1;
|
|
639
|
-
if (!args.lockInputs)
|
|
640
|
-
return;
|
|
641
|
-
for (const inputId of inputs_index)
|
|
642
|
-
cache.pending_spent_utxos[inputId] = txLogIndex;
|
|
643
|
-
},
|
|
644
|
-
});
|
|
645
|
-
const newCache = await readCacheFileDefaultLocation(this.primary_address, this.pathPrefix);
|
|
646
|
-
if (!newCache)
|
|
647
|
-
throw new Error("cache not found after send");
|
|
648
|
-
const changed_outputs = args.lockInputs
|
|
649
|
-
? args.inputs.map((output) => ({ change_reason: "spent", output }))
|
|
650
|
-
: [];
|
|
651
|
-
await this.feed({ newCache, changed_outputs });
|
|
652
|
-
await this.unpause();
|
|
653
|
-
}
|
|
654
680
|
async dismissSendPlate() {
|
|
655
681
|
await this.stopWorker();
|
|
656
682
|
await writeCacheFileDefaultLocationThrows({
|
|
@@ -218,8 +218,10 @@ export function unlockedAtHeight(output) {
|
|
|
218
218
|
export function outputStatus(output, cache, current_height) {
|
|
219
219
|
if (
|
|
220
220
|
// order matters, check this before "spent" check + "pending" check
|
|
221
|
+
// tx log index 0 is a real lock. 0 must not count as missing.
|
|
221
222
|
cache.pending_spent_utxos &&
|
|
222
|
-
cache.pending_spent_utxos[output.index_on_blockchain.toString()]
|
|
223
|
+
cache.pending_spent_utxos[output.index_on_blockchain.toString()] !==
|
|
224
|
+
undefined) {
|
|
223
225
|
return { status: "prepending" };
|
|
224
226
|
}
|
|
225
227
|
if (typeof output.burned === "number") {
|
|
@@ -58,6 +58,7 @@ export declare function processTxlogInputs(txlog: TxLog, cache: ScanCache): {
|
|
|
58
58
|
inputSum: bigint;
|
|
59
59
|
alreadyRecognizedAsSpend: boolean;
|
|
60
60
|
};
|
|
61
|
+
export declare function txLogHoldsFunds(txlog: TxLog): boolean;
|
|
61
62
|
export declare function processTxlogs(cache: ScanCache, stats: ScanStats): void;
|
|
62
63
|
export declare function alignScanStatsWithCache(cache: ScanCache, view_pair: ViewPair, primary_address: string, pathPrefix?: string, highestSubaddressMinor?: number, current_scan_tip_height?: number): Promise<ScanStats>;
|
|
63
64
|
export declare function resetStats(stats: ScanStats): void;
|
|
@@ -241,17 +241,33 @@ export function processTxlogInputs(txlog, cache) {
|
|
|
241
241
|
}
|
|
242
242
|
return { inputSum, alreadyRecognizedAsSpend };
|
|
243
243
|
}
|
|
244
|
+
// an in-flight row has no node answer yet. it still locks the inputs.
|
|
245
|
+
export function txLogHoldsFunds(txlog) {
|
|
246
|
+
if (txlog.error)
|
|
247
|
+
return false;
|
|
248
|
+
if (!txlog.sendResult)
|
|
249
|
+
return true;
|
|
250
|
+
return txlog.sendResult.status === "OK";
|
|
251
|
+
}
|
|
244
252
|
export function processTxlogs(cache, stats) {
|
|
245
253
|
for (const txlog of cache.tx_logs || []) {
|
|
246
|
-
if (!txlog ||
|
|
247
|
-
!txlog.sendResult ||
|
|
248
|
-
(txlog.sendResult && txlog.sendResult.status !== "OK"))
|
|
254
|
+
if (!txlog || !txLogHoldsFunds(txlog))
|
|
249
255
|
continue;
|
|
250
256
|
const { inputSum, alreadyRecognizedAsSpend } = processTxlogInputs(txlog, cache);
|
|
251
257
|
if (alreadyRecognizedAsSpend)
|
|
252
258
|
continue;
|
|
253
259
|
const outWardPaymentSum = processTxlogPayments(txlog, cache);
|
|
254
|
-
|
|
260
|
+
// locked inputs are already out of the spendable sum. do not subtract them again.
|
|
261
|
+
let alreadyExcluded = 0n;
|
|
262
|
+
for (const inputId of txlog.inputs_index) {
|
|
263
|
+
if (cache.pending_spent_utxos?.[inputId] === undefined)
|
|
264
|
+
continue;
|
|
265
|
+
const input = cache.outputs[inputId];
|
|
266
|
+
if (!input || typeof input.spent_in_tx_hash === "string")
|
|
267
|
+
continue;
|
|
268
|
+
alreadyExcluded += input.amount;
|
|
269
|
+
}
|
|
270
|
+
stats.total_spendable_amount -= inputSum - alreadyExcluded;
|
|
255
271
|
const newPending = inputSum - outWardPaymentSum;
|
|
256
272
|
stats.total_pending_amount += newPending;
|
|
257
273
|
}
|