pivx-wallet 0.2.0 → 0.3.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/dist/transparent-wallet.d.ts +9 -1
- package/dist/transparent-wallet.js +7 -1
- package/dist/types.d.ts +8 -0
- package/dist/wallet.d.ts +9 -0
- package/dist/wallet.js +29 -0
- package/package.json +2 -2
|
@@ -105,10 +105,18 @@ export declare class TransparentWallet {
|
|
|
105
105
|
* Like the shield wallet's sync this is a chain-data pull, not chain
|
|
106
106
|
* authentication: point it at a node you trust. See SECURITY.md.
|
|
107
107
|
*/
|
|
108
|
-
sync(client: PivxClient, { fromHeight, batchSize, onProgress }?: {
|
|
108
|
+
sync(client: PivxClient, { fromHeight, batchSize, onProgress, signal }?: {
|
|
109
109
|
fromHeight?: number;
|
|
110
110
|
batchSize?: number;
|
|
111
111
|
onProgress?: (height: number, tip: number) => void;
|
|
112
|
+
/**
|
|
113
|
+
* Abort the sync. Checked at every batch and concurrency-chunk
|
|
114
|
+
* boundary, before the next round of RPCs is issued; when set, sync
|
|
115
|
+
* throws `signal.reason` (an `AbortError` DOMException by default).
|
|
116
|
+
* Fully scanned blocks are kept and the busy guard is released, so a
|
|
117
|
+
* follow-up sync resumes where this one stopped.
|
|
118
|
+
*/
|
|
119
|
+
signal?: AbortSignal;
|
|
112
120
|
}): Promise<void>;
|
|
113
121
|
/**
|
|
114
122
|
* Total spendable transparent balance in satoshis. Outputs reserved by
|
|
@@ -207,11 +207,15 @@ export class TransparentWallet {
|
|
|
207
207
|
* Like the shield wallet's sync this is a chain-data pull, not chain
|
|
208
208
|
* authentication: point it at a node you trust. See SECURITY.md.
|
|
209
209
|
*/
|
|
210
|
-
async sync(client, { fromHeight = 0, batchSize = 100, onProgress } = {}) {
|
|
210
|
+
async sync(client, { fromHeight = 0, batchSize = 100, onProgress, signal } = {}) {
|
|
211
211
|
if (this.busy)
|
|
212
212
|
throw new Error('wallet is busy: another sync is in progress');
|
|
213
213
|
this.busy = true;
|
|
214
214
|
try {
|
|
215
|
+
const throwIfAborted = () => {
|
|
216
|
+
if (signal?.aborted)
|
|
217
|
+
throw signal.reason ?? new DOMException('sync aborted', 'AbortError');
|
|
218
|
+
};
|
|
215
219
|
const concurrency = 8;
|
|
216
220
|
const tip = await client.getBlockCount();
|
|
217
221
|
const fetchBlock = async (h) => client.getBlock(await client.getBlockHash(h), 2);
|
|
@@ -220,9 +224,11 @@ export class TransparentWallet {
|
|
|
220
224
|
const batch = Math.max(1, Math.floor(batchSize) || 1);
|
|
221
225
|
let from = Math.max(fromHeight, this.lastScanned + 1);
|
|
222
226
|
while (from <= tip) {
|
|
227
|
+
throwIfAborted(); // batch boundary: before issuing the next round of RPCs
|
|
223
228
|
const to = Math.min(from + batch - 1, tip);
|
|
224
229
|
const heights = Array.from({ length: to - from + 1 }, (_, i) => from + i);
|
|
225
230
|
for (let i = 0; i < heights.length; i += concurrency) {
|
|
231
|
+
throwIfAborted(); // chunk boundary: previous chunk fully scanned
|
|
226
232
|
const blocks = await Promise.all(heights.slice(i, i + concurrency).map(fetchBlock));
|
|
227
233
|
for (const b of blocks) {
|
|
228
234
|
// getblock verbosity 2 always carries these; a block without them
|
package/dist/types.d.ts
CHANGED
|
@@ -90,6 +90,14 @@ export interface SyncOptions {
|
|
|
90
90
|
*/
|
|
91
91
|
rpcConcurrency?: number;
|
|
92
92
|
onProgress?: (height: number, tip: number) => void;
|
|
93
|
+
/**
|
|
94
|
+
* Abort the sync. Checked at every batch and concurrency-chunk boundary,
|
|
95
|
+
* before the next round of RPCs is issued; when set, sync throws
|
|
96
|
+
* `signal.reason` (an `AbortError` DOMException by default). State stays
|
|
97
|
+
* consistent: only fully applied, root-verified batches are kept, and the
|
|
98
|
+
* busy guard is released so a follow-up sync can resume where it stopped.
|
|
99
|
+
*/
|
|
100
|
+
signal?: AbortSignal;
|
|
93
101
|
}
|
|
94
102
|
/** Serialized wallet state (spending key deliberately excluded). */
|
|
95
103
|
export interface WalletState {
|
package/dist/wallet.d.ts
CHANGED
|
@@ -60,6 +60,15 @@ export declare class PivxWallet {
|
|
|
60
60
|
recipient: string;
|
|
61
61
|
value: number;
|
|
62
62
|
} | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Remove every nullifier-map entry that is no longer referenced by a
|
|
65
|
+
* currently tracked unspent note or by a pending spend, and return the
|
|
66
|
+
* number removed. Explicit and opt-in: the map is what powers
|
|
67
|
+
* {@link getNoteFromNullifier}, so callers using nullifier → note
|
|
68
|
+
* attribution should call this only after reconciling the spends they
|
|
69
|
+
* care about. Deterministic; the save/load format is unchanged.
|
|
70
|
+
*/
|
|
71
|
+
pruneNullifiers(): number;
|
|
63
72
|
/**
|
|
64
73
|
* Scan blocks (strictly ascending heights, all above the last synced
|
|
65
74
|
* block). Returns the raw hexes of transactions relevant to this wallet.
|
package/dist/wallet.js
CHANGED
|
@@ -202,6 +202,28 @@ export class PivxWallet {
|
|
|
202
202
|
getNoteFromNullifier(nullifier) {
|
|
203
203
|
return this.nullifierMap.get(nullifier);
|
|
204
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Remove every nullifier-map entry that is no longer referenced by a
|
|
207
|
+
* currently tracked unspent note or by a pending spend, and return the
|
|
208
|
+
* number removed. Explicit and opt-in: the map is what powers
|
|
209
|
+
* {@link getNoteFromNullifier}, so callers using nullifier → note
|
|
210
|
+
* attribution should call this only after reconciling the spends they
|
|
211
|
+
* care about. Deterministic; the save/load format is unchanged.
|
|
212
|
+
*/
|
|
213
|
+
pruneNullifiers() {
|
|
214
|
+
const live = new Set(this.notes.map((n) => n.nullifier));
|
|
215
|
+
for (const nulls of this.pendingSpends.values())
|
|
216
|
+
for (const n of nulls)
|
|
217
|
+
live.add(n);
|
|
218
|
+
let removed = 0;
|
|
219
|
+
for (const nullifier of this.nullifierMap.keys()) {
|
|
220
|
+
if (!live.has(nullifier)) {
|
|
221
|
+
this.nullifierMap.delete(nullifier);
|
|
222
|
+
removed++;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return removed;
|
|
226
|
+
}
|
|
205
227
|
// ── Scanning ──────────────────────────────────────────────────────────────
|
|
206
228
|
/**
|
|
207
229
|
* Scan blocks (strictly ascending heights, all above the last synced
|
|
@@ -286,6 +308,11 @@ export class PivxWallet {
|
|
|
286
308
|
throw new Error('wallet is busy: another sync or spend is in progress');
|
|
287
309
|
this.busy = true;
|
|
288
310
|
try {
|
|
311
|
+
const throwIfAborted = () => {
|
|
312
|
+
if (opts.signal?.aborted) {
|
|
313
|
+
throw opts.signal.reason ?? new DOMException('sync aborted', 'AbortError');
|
|
314
|
+
}
|
|
315
|
+
};
|
|
289
316
|
// NaN/0/fractional → sane integer; 0 would loop forever.
|
|
290
317
|
const batchSize = Math.max(1, Math.floor(opts.batchSize ?? 100) || 1);
|
|
291
318
|
// getblock verbosity 2 is heavy. A default node has 4 RPC threads and a
|
|
@@ -306,11 +333,13 @@ export class PivxWallet {
|
|
|
306
333
|
return block;
|
|
307
334
|
};
|
|
308
335
|
while (this.lastProcessedBlock < tip) {
|
|
336
|
+
throwIfAborted(); // batch boundary: nothing applied yet, state consistent
|
|
309
337
|
const from = this.lastProcessedBlock + 1;
|
|
310
338
|
const to = Math.min(from + batchSize - 1, tip);
|
|
311
339
|
const heights = Array.from({ length: to - from + 1 }, (_, i) => from + i);
|
|
312
340
|
const blocks = [];
|
|
313
341
|
for (let i = 0; i < heights.length; i += concurrency) {
|
|
342
|
+
throwIfAborted(); // chunk boundary: before issuing the next RPCs
|
|
314
343
|
blocks.push(...(await Promise.all(heights.slice(i, i + concurrency).map(fetchBlock))));
|
|
315
344
|
}
|
|
316
345
|
// Snapshot so a failed root check can't leave partial state behind.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pivx-wallet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Standalone PIVX wallet SDK: local key management, shielded (SHIELD/Sapling) scanning, balances, and transaction building. The node is only a chain-data source.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@noble/hashes": "^2.2.0",
|
|
45
45
|
"@scure/base": "^2.2.0",
|
|
46
46
|
"@scure/bip32": "^2.2.0",
|
|
47
|
-
"pivx-rpc": "^0.
|
|
47
|
+
"pivx-rpc": "^0.2.0",
|
|
48
48
|
"pivx-shield-rust": "^1.4.0"
|
|
49
49
|
},
|
|
50
50
|
"optionalDependencies": {
|