@arkade-os/sdk 0.4.16 → 0.4.18
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 +16 -6
- package/dist/cjs/contracts/arkcontract.js +0 -2
- package/dist/cjs/contracts/contractManager.js +111 -199
- package/dist/cjs/contracts/contractWatcher.js +86 -115
- package/dist/cjs/repositories/indexedDB/manager.js +6 -3
- package/dist/cjs/repositories/indexedDB/schema.js +47 -2
- package/dist/cjs/repositories/indexedDB/walletRepository.js +21 -2
- package/dist/cjs/repositories/realm/contractRepository.js +0 -4
- package/dist/cjs/repositories/realm/index.js +3 -1
- package/dist/cjs/repositories/realm/schemas.js +50 -1
- package/dist/cjs/repositories/realm/walletRepository.js +8 -4
- package/dist/cjs/repositories/scriptFromAddress.js +16 -0
- package/dist/cjs/repositories/sqlite/contractRepository.js +2 -6
- package/dist/cjs/repositories/sqlite/walletRepository.js +121 -33
- package/dist/cjs/utils/syncCursors.js +48 -56
- package/dist/cjs/wallet/expo/background.js +0 -13
- package/dist/cjs/wallet/expo/wallet.js +1 -6
- package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +16 -7
- package/dist/cjs/wallet/serviceWorker/wallet.js +19 -0
- package/dist/cjs/wallet/utils.js +41 -10
- package/dist/cjs/wallet/vtxo-manager.js +153 -39
- package/dist/cjs/wallet/wallet.js +84 -202
- package/dist/cjs/worker/expo/processors/contractPollProcessor.js +9 -13
- package/dist/cjs/worker/expo/taskRunner.js +2 -11
- package/dist/esm/contracts/arkcontract.js +0 -2
- package/dist/esm/contracts/contractManager.js +113 -201
- package/dist/esm/contracts/contractWatcher.js +86 -115
- package/dist/esm/repositories/indexedDB/manager.js +6 -3
- package/dist/esm/repositories/indexedDB/schema.js +46 -2
- package/dist/esm/repositories/indexedDB/walletRepository.js +21 -2
- package/dist/esm/repositories/realm/contractRepository.js +0 -4
- package/dist/esm/repositories/realm/index.js +1 -1
- package/dist/esm/repositories/realm/schemas.js +48 -0
- package/dist/esm/repositories/realm/walletRepository.js +8 -4
- package/dist/esm/repositories/scriptFromAddress.js +13 -0
- package/dist/esm/repositories/sqlite/contractRepository.js +2 -6
- package/dist/esm/repositories/sqlite/walletRepository.js +121 -33
- package/dist/esm/utils/syncCursors.js +47 -53
- package/dist/esm/wallet/expo/background.js +0 -13
- package/dist/esm/wallet/expo/wallet.js +2 -7
- package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +17 -8
- package/dist/esm/wallet/serviceWorker/wallet.js +19 -0
- package/dist/esm/wallet/utils.js +41 -9
- package/dist/esm/wallet/vtxo-manager.js +153 -39
- package/dist/esm/wallet/wallet.js +87 -205
- package/dist/esm/worker/expo/processors/contractPollProcessor.js +9 -13
- package/dist/esm/worker/expo/taskRunner.js +3 -12
- package/dist/types/contracts/arkcontract.d.ts +0 -2
- package/dist/types/contracts/contractManager.d.ts +38 -12
- package/dist/types/contracts/contractWatcher.d.ts +22 -21
- package/dist/types/contracts/types.d.ts +0 -7
- package/dist/types/repositories/indexedDB/manager.d.ts +5 -2
- package/dist/types/repositories/indexedDB/schema.d.ts +3 -2
- package/dist/types/repositories/realm/index.d.ts +1 -1
- package/dist/types/repositories/realm/schemas.d.ts +41 -0
- package/dist/types/repositories/scriptFromAddress.d.ts +9 -0
- package/dist/types/repositories/serialization.d.ts +1 -1
- package/dist/types/repositories/sqlite/walletRepository.d.ts +22 -0
- package/dist/types/repositories/walletRepository.d.ts +10 -2
- package/dist/types/utils/syncCursors.d.ts +25 -23
- package/dist/types/wallet/index.d.ts +1 -1
- package/dist/types/wallet/serviceWorker/wallet-message-handler.d.ts +15 -3
- package/dist/types/wallet/utils.d.ts +20 -4
- package/dist/types/wallet/vtxo-manager.d.ts +16 -6
- package/dist/types/wallet/wallet.d.ts +5 -17
- package/dist/types/worker/expo/processors/contractPollProcessor.d.ts +9 -4
- package/dist/types/worker/expo/taskRunner.d.ts +6 -3
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { hex } from "@scure/base";
|
|
2
2
|
import { ContractWatcher } from './contractWatcher.js';
|
|
3
3
|
import { contractHandlers } from './handlers/index.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { extendVirtualCoinForContract } from '../wallet/utils.js';
|
|
5
|
+
import { advanceSyncCursor, computeSyncWindow, cursorCutoff, getSyncCursor, } from '../utils/syncCursors.js';
|
|
6
6
|
const DEFAULT_PAGE_SIZE = 500;
|
|
7
7
|
/**
|
|
8
8
|
* Central manager for contract lifecycle and operations.
|
|
@@ -11,7 +11,7 @@ const DEFAULT_PAGE_SIZE = 500;
|
|
|
11
11
|
* - Create and persist contracts
|
|
12
12
|
* - Query stored contracts (optionally with their virtual outputs)
|
|
13
13
|
* - Provide spendable path selection for a contract
|
|
14
|
-
* - Emit contract-related events (virtual output received/spent
|
|
14
|
+
* - Emit contract-related events (virtual output received/spent, connection reset)
|
|
15
15
|
*
|
|
16
16
|
* Notes:
|
|
17
17
|
* - Implementations typically start watching automatically during initialization
|
|
@@ -22,8 +22,6 @@ const DEFAULT_PAGE_SIZE = 500;
|
|
|
22
22
|
* const manager = await ContractManager.create({
|
|
23
23
|
* indexerProvider: wallet.indexerProvider,
|
|
24
24
|
* contractRepository: wallet.contractRepository,
|
|
25
|
-
* walletRepository: wallet.walletRepository,
|
|
26
|
-
* getDefaultAddress: () => wallet.getAddress(),
|
|
27
25
|
* });
|
|
28
26
|
*
|
|
29
27
|
* // Create a new VHTLC contract
|
|
@@ -83,29 +81,16 @@ export class ContractManager {
|
|
|
83
81
|
if (this.initialized) {
|
|
84
82
|
return;
|
|
85
83
|
}
|
|
86
|
-
//
|
|
84
|
+
// Register persisted contracts with the watcher BEFORE the first
|
|
85
|
+
// sync. `addContract` seeds `lastKnownVtxos` from the repo without
|
|
86
|
+
// starting to poll, so it's cheap, and it populates
|
|
87
|
+
// `getWatchedContracts()` so the sync below can scope itself to the
|
|
88
|
+
// real watched set instead of every contract ever persisted.
|
|
87
89
|
const contracts = await this.config.contractRepository.getContracts();
|
|
88
|
-
// Delta-sync: fetch only virtual outputs that changed since the last cursor,
|
|
89
|
-
// falling back to a full bootstrap for scripts seen for the first time.
|
|
90
|
-
await this.deltaSyncContracts(contracts);
|
|
91
|
-
// Reconcile the pending frontier: fetch all not-yet-finalized virtual outputs
|
|
92
|
-
// to catch any that the delta window may have missed.
|
|
93
|
-
if (contracts.length > 0) {
|
|
94
|
-
await this.reconcilePendingFrontier(contracts);
|
|
95
|
-
}
|
|
96
|
-
// add all contracts to the watcher
|
|
97
|
-
const now = Date.now();
|
|
98
90
|
for (const contract of contracts) {
|
|
99
|
-
// Check for expired contracts and mark as inactive
|
|
100
|
-
if (contract.state === "active" &&
|
|
101
|
-
contract.expiresAt &&
|
|
102
|
-
contract.expiresAt <= now) {
|
|
103
|
-
contract.state = "inactive";
|
|
104
|
-
await this.config.contractRepository.saveContract(contract);
|
|
105
|
-
}
|
|
106
|
-
// Add to watcher
|
|
107
91
|
await this.watcher.addContract(contract);
|
|
108
92
|
}
|
|
93
|
+
await this.reconcileWatched();
|
|
109
94
|
this.initialized = true;
|
|
110
95
|
// Start watching automatically
|
|
111
96
|
this.stopWatcherFn = await this.watcher.startWatching((event) => {
|
|
@@ -114,6 +99,23 @@ export class ContractManager {
|
|
|
114
99
|
});
|
|
115
100
|
});
|
|
116
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Delta-sync the full watched set and reconcile the pending frontier.
|
|
104
|
+
*
|
|
105
|
+
* Shared recovery path used on initial boot and after a subscription
|
|
106
|
+
* reconnect. `syncContracts({})` scopes to the current watched set
|
|
107
|
+
* (see {@link ContractWatcher.getWatchedContracts}), uses the
|
|
108
|
+
* cursor-derived delta window, and advances the cursor on success.
|
|
109
|
+
* `reconcilePendingFrontier` catches not-yet-finalized virtual
|
|
110
|
+
* outputs that could sit outside any delta window.
|
|
111
|
+
*/
|
|
112
|
+
async reconcileWatched() {
|
|
113
|
+
await this.syncContracts({});
|
|
114
|
+
const watched = this.watcher.getWatchedContracts();
|
|
115
|
+
if (watched.length > 0) {
|
|
116
|
+
await this.reconcilePendingFrontier(watched);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
117
119
|
/**
|
|
118
120
|
* Create and register a new contract.
|
|
119
121
|
*
|
|
@@ -158,15 +160,7 @@ export class ContractManager {
|
|
|
158
160
|
// Persist
|
|
159
161
|
await this.config.contractRepository.saveContract(contract);
|
|
160
162
|
// fetch all virtual outputs (including spent/swept) for this contract
|
|
161
|
-
|
|
162
|
-
await this.fetchContractVxosFromIndexer([contract], true);
|
|
163
|
-
// Advance the sync cursor so that the watcher's vtxo_received
|
|
164
|
-
// event (triggered by addContract below) doesn't re-bootstrap
|
|
165
|
-
// the same script via deltaSyncContracts.
|
|
166
|
-
const cutoff = cursorCutoff(requestStartedAt);
|
|
167
|
-
await advanceSyncCursors(this.config.walletRepository, {
|
|
168
|
-
[contract.script]: cutoff,
|
|
169
|
-
});
|
|
163
|
+
await this.fetchContractVxosFromIndexer([contract]);
|
|
170
164
|
// Add to watcher
|
|
171
165
|
await this.watcher.addContract(contract);
|
|
172
166
|
return contract;
|
|
@@ -192,12 +186,26 @@ export class ContractManager {
|
|
|
192
186
|
}
|
|
193
187
|
async getContractsWithVtxos(filter, pageSize) {
|
|
194
188
|
const contracts = await this.getContracts(filter);
|
|
195
|
-
|
|
189
|
+
await this.syncContracts({ contracts, pageSize });
|
|
190
|
+
const vtxos = await this.getVtxosForContracts(contracts);
|
|
196
191
|
return contracts.map((contract) => ({
|
|
197
192
|
contract,
|
|
198
|
-
vtxos: vtxos.
|
|
193
|
+
vtxos: vtxos.filter((vtxo) => vtxo.contractScript === contract.script),
|
|
199
194
|
}));
|
|
200
195
|
}
|
|
196
|
+
async annotateVtxos(vtxos) {
|
|
197
|
+
if (vtxos.length === 0)
|
|
198
|
+
return [];
|
|
199
|
+
const scripts = Array.from(new Set(vtxos.map((v) => v.script)));
|
|
200
|
+
const byScript = new Map();
|
|
201
|
+
const contracts = await this.config.contractRepository.getContracts({
|
|
202
|
+
script: scripts,
|
|
203
|
+
});
|
|
204
|
+
for (const contract of contracts) {
|
|
205
|
+
byScript.set(contract.script, contract);
|
|
206
|
+
}
|
|
207
|
+
return vtxos.map((vtxo) => extendVirtualCoinForContract(vtxo, byScript));
|
|
208
|
+
}
|
|
201
209
|
buildContractsDbFilter(filter) {
|
|
202
210
|
return {
|
|
203
211
|
script: filter.script,
|
|
@@ -338,41 +346,18 @@ export class ContractManager {
|
|
|
338
346
|
/**
|
|
339
347
|
* Force refresh virtual outputs from the indexer.
|
|
340
348
|
*
|
|
341
|
-
* Without options,
|
|
349
|
+
* Without options, re-fetches every contract and advances the global cursor.
|
|
342
350
|
* With options, narrows the refresh to specific scripts and/or a time window.
|
|
351
|
+
* Subset refreshes (scripts filter) intentionally do not advance the cursor.
|
|
343
352
|
*/
|
|
344
353
|
async refreshVtxos(opts) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
const scriptSet = new Set(opts.scripts);
|
|
348
|
-
contracts = contracts.filter((c) => scriptSet.has(c.script));
|
|
349
|
-
}
|
|
350
|
-
const syncWindow = opts?.after !== undefined || opts?.before !== undefined
|
|
351
|
-
? {
|
|
352
|
-
after: opts.after ?? 0,
|
|
353
|
-
before: opts.before ?? Date.now(),
|
|
354
|
-
}
|
|
354
|
+
const contracts = opts?.scripts
|
|
355
|
+
? await this.getContracts({ script: opts.scripts })
|
|
355
356
|
: undefined;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
else {
|
|
362
|
-
await clearSyncCursors(this.config.walletRepository);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
const requestStartedAt = Date.now();
|
|
366
|
-
const fetched = await this.fetchContractVxosFromIndexer(contracts, true, undefined, syncWindow);
|
|
367
|
-
// Persist cursors so subsequent incremental syncs don't re-bootstrap.
|
|
368
|
-
const cutoff = cursorCutoff(requestStartedAt);
|
|
369
|
-
const cursorUpdates = {};
|
|
370
|
-
for (const script of fetched.keys()) {
|
|
371
|
-
cursorUpdates[script] = cutoff;
|
|
372
|
-
}
|
|
373
|
-
if (Object.keys(cursorUpdates).length > 0) {
|
|
374
|
-
await advanceSyncCursors(this.config.walletRepository, cursorUpdates);
|
|
375
|
-
}
|
|
357
|
+
await this.syncContracts({
|
|
358
|
+
contracts,
|
|
359
|
+
window: { after: opts?.after, before: opts?.before },
|
|
360
|
+
});
|
|
376
361
|
}
|
|
377
362
|
/**
|
|
378
363
|
* Check if currently watching.
|
|
@@ -401,76 +386,54 @@ export class ContractManager {
|
|
|
401
386
|
// Delta-sync only the changed virtual outputs for this contract.
|
|
402
387
|
case "vtxo_received":
|
|
403
388
|
case "vtxo_spent":
|
|
404
|
-
await this.
|
|
389
|
+
await this.syncContracts({ contracts: [event.contract] });
|
|
405
390
|
break;
|
|
406
|
-
case "connection_reset":
|
|
407
|
-
//
|
|
408
|
-
|
|
409
|
-
|
|
391
|
+
case "connection_reset":
|
|
392
|
+
// Same recovery path as boot: delta-sync the watched set
|
|
393
|
+
// and reconcile the pending frontier. `advanceSyncCursor`
|
|
394
|
+
// is monotonic so this never rewinds the cursor.
|
|
395
|
+
await this.reconcileWatched();
|
|
410
396
|
break;
|
|
411
|
-
}
|
|
412
|
-
case "contract_expired":
|
|
413
|
-
// just update DB
|
|
414
|
-
await this.config.contractRepository.saveContract(event.contract);
|
|
415
397
|
}
|
|
416
398
|
// Forward to all callbacks
|
|
417
399
|
this.emitEvent(event);
|
|
418
400
|
}
|
|
419
|
-
async getVtxosForContracts(contracts
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
401
|
+
async getVtxosForContracts(contracts) {
|
|
402
|
+
const res = await Promise.all(contracts.map(({ script, address }) => this.config.walletRepository.getVtxos(address).then((vtxos) => vtxos.map((vtxo) => ({
|
|
403
|
+
...vtxo,
|
|
404
|
+
contractScript: script,
|
|
405
|
+
})))));
|
|
406
|
+
return res.flat();
|
|
424
407
|
}
|
|
425
408
|
/**
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
409
|
+
* Sync virtual outputs for the given contracts against the indexer.
|
|
410
|
+
*
|
|
411
|
+
* When `options.contracts` is omitted the sync covers the full
|
|
412
|
+
* watched set (active contracts plus any inactive contracts still
|
|
413
|
+
* holding cached VTXOs) and the global cursor is advanced on
|
|
414
|
+
* success. Passing an explicit subset leaves the cursor alone so a
|
|
415
|
+
* narrow poll can't hide data that other contracts still need to
|
|
416
|
+
* pick up.
|
|
429
417
|
*/
|
|
430
|
-
async
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
const result =
|
|
446
|
-
|
|
447
|
-
// Full bootstrap for new scripts.
|
|
448
|
-
if (bootstrap.length > 0) {
|
|
449
|
-
const requestStartedAt = Date.now();
|
|
450
|
-
const fetched = await this.fetchContractVxosFromIndexer(bootstrap, true);
|
|
418
|
+
async syncContracts(options) {
|
|
419
|
+
const cursor = await getSyncCursor(this.config.walletRepository);
|
|
420
|
+
const window = options.window ?? computeSyncWindow(cursor);
|
|
421
|
+
// Advance the global cursor only on full-scope, cursor-derived delta
|
|
422
|
+
// syncs. A caller-supplied window is targeted (e.g. `refreshVtxos`)
|
|
423
|
+
// and must not move the cursor — it may skip data outside its bounds.
|
|
424
|
+
// `<=` lets the bootstrap case (cursor=0, window.after=0) write the
|
|
425
|
+
// migration marker on first boot; otherwise the marker would never
|
|
426
|
+
// be written and every subsequent boot would treat the cursor as
|
|
427
|
+
// legacy and re-bootstrap.
|
|
428
|
+
const mustUpdateCursor = options.contracts === undefined &&
|
|
429
|
+
options.window === undefined &&
|
|
430
|
+
(window.after ?? 0) <= cursor;
|
|
431
|
+
const contracts = options.contracts ?? this.watcher.getWatchedContracts();
|
|
432
|
+
const requestStartedAt = Date.now();
|
|
433
|
+
const result = await this.fetchContractVxosFromIndexer(contracts, options.pageSize, window);
|
|
434
|
+
if (mustUpdateCursor) {
|
|
451
435
|
const cutoff = cursorCutoff(requestStartedAt);
|
|
452
|
-
|
|
453
|
-
result.set(script, vtxos);
|
|
454
|
-
cursorUpdates[script] = cutoff;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
// Delta sync for scripts with an existing cursor.
|
|
458
|
-
if (delta.length > 0) {
|
|
459
|
-
// Use the oldest cursor so the shared window covers every script.
|
|
460
|
-
const minCursor = Math.min(...delta.map((c) => cursors[c.script]));
|
|
461
|
-
const window = computeSyncWindow(minCursor);
|
|
462
|
-
if (window) {
|
|
463
|
-
const requestStartedAt = Date.now();
|
|
464
|
-
const fetched = await this.fetchContractVxosFromIndexer(delta, true, pageSize, window);
|
|
465
|
-
const cutoff = cursorCutoff(requestStartedAt);
|
|
466
|
-
for (const [script, vtxos] of fetched) {
|
|
467
|
-
result.set(script, vtxos);
|
|
468
|
-
cursorUpdates[script] = cutoff;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
if (Object.keys(cursorUpdates).length > 0) {
|
|
473
|
-
await advanceSyncCursors(this.config.walletRepository, cursorUpdates);
|
|
436
|
+
await advanceSyncCursor(this.config.walletRepository, cutoff);
|
|
474
437
|
}
|
|
475
438
|
return result;
|
|
476
439
|
}
|
|
@@ -486,21 +449,20 @@ export class ContractManager {
|
|
|
486
449
|
scripts,
|
|
487
450
|
pendingOnly: true,
|
|
488
451
|
});
|
|
489
|
-
//
|
|
452
|
+
// Share the annotation path with external callers so the two entry
|
|
453
|
+
// points can't drift.
|
|
454
|
+
const owned = vtxos.filter((v) => scriptToContract.has(v.script));
|
|
455
|
+
const annotated = await this.annotateVtxos(owned);
|
|
490
456
|
const byContract = new Map();
|
|
491
|
-
for (const vtxo of
|
|
492
|
-
if (!vtxo.script)
|
|
493
|
-
continue;
|
|
457
|
+
for (const vtxo of annotated) {
|
|
494
458
|
const contract = scriptToContract.get(vtxo.script);
|
|
495
|
-
if (!contract)
|
|
496
|
-
continue;
|
|
497
459
|
let arr = byContract.get(contract.address);
|
|
498
460
|
if (!arr) {
|
|
499
461
|
arr = [];
|
|
500
462
|
byContract.set(contract.address, arr);
|
|
501
463
|
}
|
|
502
464
|
arr.push({
|
|
503
|
-
...
|
|
465
|
+
...vtxo,
|
|
504
466
|
contractScript: contract.script,
|
|
505
467
|
});
|
|
506
468
|
}
|
|
@@ -508,8 +470,8 @@ export class ContractManager {
|
|
|
508
470
|
await this.config.walletRepository.saveVtxos(addr, contractVtxos);
|
|
509
471
|
}
|
|
510
472
|
}
|
|
511
|
-
async fetchContractVxosFromIndexer(contracts,
|
|
512
|
-
const fetched = await this.fetchContractVtxosBulk(contracts,
|
|
473
|
+
async fetchContractVxosFromIndexer(contracts, pageSize, syncWindow) {
|
|
474
|
+
const fetched = await this.fetchContractVtxosBulk(contracts, pageSize, syncWindow);
|
|
513
475
|
const result = new Map();
|
|
514
476
|
for (const [contractScript, vtxos] of fetched) {
|
|
515
477
|
result.set(contractScript, vtxos);
|
|
@@ -520,23 +482,17 @@ export class ContractManager {
|
|
|
520
482
|
}
|
|
521
483
|
return result;
|
|
522
484
|
}
|
|
523
|
-
async fetchContractVtxosBulk(contracts,
|
|
485
|
+
async fetchContractVtxosBulk(contracts, pageSize = DEFAULT_PAGE_SIZE, syncWindow) {
|
|
524
486
|
if (contracts.length === 0) {
|
|
525
487
|
return new Map();
|
|
526
488
|
}
|
|
527
|
-
//
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
return new Map([[contract.script, vtxos]]);
|
|
532
|
-
}
|
|
533
|
-
// For multiple contracts, batch all scripts into a single indexer call
|
|
534
|
-
// per page to minimise round-trips. Results are keyed by script so we
|
|
535
|
-
// can distribute them back to the correct contract afterwards.
|
|
489
|
+
// Batch all scripts into a single indexer call per page to minimise
|
|
490
|
+
// round-trips. Results are keyed by script so we can distribute them
|
|
491
|
+
// back to the correct contract afterwards. Always fetches the full
|
|
492
|
+
// history (spent/swept included) so the repo is the source of truth.
|
|
536
493
|
const scriptToContract = new Map(contracts.map((c) => [c.script, c]));
|
|
537
494
|
const result = new Map(contracts.map((c) => [c.script, []]));
|
|
538
495
|
const scripts = contracts.map((c) => c.script);
|
|
539
|
-
const opts = includeSpent ? {} : { spendableOnly: true };
|
|
540
496
|
const windowOpts = syncWindow
|
|
541
497
|
? {
|
|
542
498
|
...(syncWindow.after !== undefined && {
|
|
@@ -552,22 +508,20 @@ export class ContractManager {
|
|
|
552
508
|
while (hasMore) {
|
|
553
509
|
const { vtxos, page } = await this.config.indexerProvider.getVtxos({
|
|
554
510
|
scripts,
|
|
555
|
-
...opts,
|
|
556
511
|
...windowOpts,
|
|
557
512
|
pageIndex,
|
|
558
513
|
pageSize,
|
|
559
514
|
});
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
contractScript: contract.script,
|
|
515
|
+
// Match virtual outputs back to their contract via the script field
|
|
516
|
+
// populated by the indexer, then share the annotation path with
|
|
517
|
+
// external callers via annotateVtxos so the two entry points can't
|
|
518
|
+
// drift.
|
|
519
|
+
const owned = vtxos.filter((v) => scriptToContract.has(v.script));
|
|
520
|
+
const annotated = await this.annotateVtxos(owned);
|
|
521
|
+
for (const vtxo of annotated) {
|
|
522
|
+
result.get(vtxo.script).push({
|
|
523
|
+
...vtxo,
|
|
524
|
+
contractScript: vtxo.script,
|
|
571
525
|
});
|
|
572
526
|
}
|
|
573
527
|
hasMore = page ? vtxos.length === pageSize : false;
|
|
@@ -577,42 +531,6 @@ export class ContractManager {
|
|
|
577
531
|
}
|
|
578
532
|
return result;
|
|
579
533
|
}
|
|
580
|
-
async fetchContractVtxosPaginated(contract, includeSpent, pageSize = DEFAULT_PAGE_SIZE, syncWindow) {
|
|
581
|
-
const allVtxos = [];
|
|
582
|
-
let pageIndex = 0;
|
|
583
|
-
let hasMore = true;
|
|
584
|
-
const opts = includeSpent ? {} : { spendableOnly: true };
|
|
585
|
-
const windowOpts = syncWindow
|
|
586
|
-
? {
|
|
587
|
-
...(syncWindow.after !== undefined && {
|
|
588
|
-
after: syncWindow.after,
|
|
589
|
-
}),
|
|
590
|
-
...(syncWindow.before !== undefined && {
|
|
591
|
-
before: syncWindow.before,
|
|
592
|
-
}),
|
|
593
|
-
}
|
|
594
|
-
: {};
|
|
595
|
-
while (hasMore) {
|
|
596
|
-
const { vtxos, page } = await this.config.indexerProvider.getVtxos({
|
|
597
|
-
scripts: [contract.script],
|
|
598
|
-
...opts,
|
|
599
|
-
...windowOpts,
|
|
600
|
-
pageIndex,
|
|
601
|
-
pageSize,
|
|
602
|
-
});
|
|
603
|
-
for (const vtxo of vtxos) {
|
|
604
|
-
allVtxos.push({
|
|
605
|
-
...extendVtxoFromContract(vtxo, contract),
|
|
606
|
-
contractScript: contract.script,
|
|
607
|
-
});
|
|
608
|
-
}
|
|
609
|
-
hasMore = page ? vtxos.length === pageSize : false;
|
|
610
|
-
pageIndex++;
|
|
611
|
-
if (hasMore)
|
|
612
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
613
|
-
}
|
|
614
|
-
return allVtxos;
|
|
615
|
-
}
|
|
616
534
|
/**
|
|
617
535
|
* Dispose of the ContractManager and release all resources.
|
|
618
536
|
*
|
|
@@ -641,12 +559,6 @@ export class ContractManager {
|
|
|
641
559
|
* ```
|
|
642
560
|
*/
|
|
643
561
|
[Symbol.dispose]() {
|
|
644
|
-
|
|
645
|
-
this.stopWatcherFn?.();
|
|
646
|
-
this.stopWatcherFn = undefined;
|
|
647
|
-
// Clear callbacks
|
|
648
|
-
this.eventCallbacks.clear();
|
|
649
|
-
// Mark as uninitialized
|
|
650
|
-
this.initialized = false;
|
|
562
|
+
this.dispose();
|
|
651
563
|
}
|
|
652
564
|
}
|