@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.
Files changed (68) hide show
  1. package/README.md +16 -6
  2. package/dist/cjs/contracts/arkcontract.js +0 -2
  3. package/dist/cjs/contracts/contractManager.js +111 -199
  4. package/dist/cjs/contracts/contractWatcher.js +86 -115
  5. package/dist/cjs/repositories/indexedDB/manager.js +6 -3
  6. package/dist/cjs/repositories/indexedDB/schema.js +47 -2
  7. package/dist/cjs/repositories/indexedDB/walletRepository.js +21 -2
  8. package/dist/cjs/repositories/realm/contractRepository.js +0 -4
  9. package/dist/cjs/repositories/realm/index.js +3 -1
  10. package/dist/cjs/repositories/realm/schemas.js +50 -1
  11. package/dist/cjs/repositories/realm/walletRepository.js +8 -4
  12. package/dist/cjs/repositories/scriptFromAddress.js +16 -0
  13. package/dist/cjs/repositories/sqlite/contractRepository.js +2 -6
  14. package/dist/cjs/repositories/sqlite/walletRepository.js +121 -33
  15. package/dist/cjs/utils/syncCursors.js +48 -56
  16. package/dist/cjs/wallet/expo/background.js +0 -13
  17. package/dist/cjs/wallet/expo/wallet.js +1 -6
  18. package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +16 -7
  19. package/dist/cjs/wallet/serviceWorker/wallet.js +19 -0
  20. package/dist/cjs/wallet/utils.js +41 -10
  21. package/dist/cjs/wallet/vtxo-manager.js +153 -39
  22. package/dist/cjs/wallet/wallet.js +84 -202
  23. package/dist/cjs/worker/expo/processors/contractPollProcessor.js +9 -13
  24. package/dist/cjs/worker/expo/taskRunner.js +2 -11
  25. package/dist/esm/contracts/arkcontract.js +0 -2
  26. package/dist/esm/contracts/contractManager.js +113 -201
  27. package/dist/esm/contracts/contractWatcher.js +86 -115
  28. package/dist/esm/repositories/indexedDB/manager.js +6 -3
  29. package/dist/esm/repositories/indexedDB/schema.js +46 -2
  30. package/dist/esm/repositories/indexedDB/walletRepository.js +21 -2
  31. package/dist/esm/repositories/realm/contractRepository.js +0 -4
  32. package/dist/esm/repositories/realm/index.js +1 -1
  33. package/dist/esm/repositories/realm/schemas.js +48 -0
  34. package/dist/esm/repositories/realm/walletRepository.js +8 -4
  35. package/dist/esm/repositories/scriptFromAddress.js +13 -0
  36. package/dist/esm/repositories/sqlite/contractRepository.js +2 -6
  37. package/dist/esm/repositories/sqlite/walletRepository.js +121 -33
  38. package/dist/esm/utils/syncCursors.js +47 -53
  39. package/dist/esm/wallet/expo/background.js +0 -13
  40. package/dist/esm/wallet/expo/wallet.js +2 -7
  41. package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +17 -8
  42. package/dist/esm/wallet/serviceWorker/wallet.js +19 -0
  43. package/dist/esm/wallet/utils.js +41 -9
  44. package/dist/esm/wallet/vtxo-manager.js +153 -39
  45. package/dist/esm/wallet/wallet.js +87 -205
  46. package/dist/esm/worker/expo/processors/contractPollProcessor.js +9 -13
  47. package/dist/esm/worker/expo/taskRunner.js +3 -12
  48. package/dist/types/contracts/arkcontract.d.ts +0 -2
  49. package/dist/types/contracts/contractManager.d.ts +38 -12
  50. package/dist/types/contracts/contractWatcher.d.ts +22 -21
  51. package/dist/types/contracts/types.d.ts +0 -7
  52. package/dist/types/repositories/indexedDB/manager.d.ts +5 -2
  53. package/dist/types/repositories/indexedDB/schema.d.ts +3 -2
  54. package/dist/types/repositories/realm/index.d.ts +1 -1
  55. package/dist/types/repositories/realm/schemas.d.ts +41 -0
  56. package/dist/types/repositories/scriptFromAddress.d.ts +9 -0
  57. package/dist/types/repositories/serialization.d.ts +1 -1
  58. package/dist/types/repositories/sqlite/walletRepository.d.ts +22 -0
  59. package/dist/types/repositories/walletRepository.d.ts +10 -2
  60. package/dist/types/utils/syncCursors.d.ts +25 -23
  61. package/dist/types/wallet/index.d.ts +1 -1
  62. package/dist/types/wallet/serviceWorker/wallet-message-handler.d.ts +15 -3
  63. package/dist/types/wallet/utils.d.ts +20 -4
  64. package/dist/types/wallet/vtxo-manager.d.ts +16 -6
  65. package/dist/types/wallet/wallet.d.ts +5 -17
  66. package/dist/types/worker/expo/processors/contractPollProcessor.d.ts +9 -4
  67. package/dist/types/worker/expo/taskRunner.d.ts +6 -3
  68. package/package.json +1 -1
package/README.md CHANGED
@@ -878,9 +878,22 @@ const wallet = await Wallet.create({
878
878
  For React Native apps using Realm, pass your Realm instance directly:
879
879
 
880
880
  ```typescript
881
- import { RealmWalletRepository, RealmContractRepository, ArkRealmSchemas } from '@arkade-os/sdk/repositories/realm'
882
-
883
- const realm = await Realm.open({ schema: [...ArkRealmSchemas, ...yourSchemas] })
881
+ import {
882
+ RealmWalletRepository,
883
+ RealmContractRepository,
884
+ ArkRealmSchemas,
885
+ ARK_REALM_SCHEMA_VERSION,
886
+ runArkRealmMigrations,
887
+ } from '@arkade-os/sdk/repositories/realm'
888
+
889
+ const realm = await Realm.open({
890
+ schema: [...ArkRealmSchemas, ...yourSchemas],
891
+ schemaVersion: Math.max(ARK_REALM_SCHEMA_VERSION, yourSchemaVersion),
892
+ onMigration: (oldRealm, newRealm) => {
893
+ runArkRealmMigrations(oldRealm, newRealm)
894
+ // your own migrations
895
+ },
896
+ })
884
897
  const wallet = await Wallet.create({
885
898
  identity,
886
899
  arkServerUrl: 'https://arkade.computer',
@@ -1074,9 +1087,6 @@ const unsubscribe = await manager.onContractEvent((event) => {
1074
1087
  case 'vtxo_spent':
1075
1088
  console.log(`Spent virtual outputs from ${event.contractScript}`)
1076
1089
  break
1077
- case 'contract_expired':
1078
- console.log(`Contract ${event.contractScript} expired`)
1079
- break
1080
1090
  }
1081
1091
  })
1082
1092
 
@@ -110,7 +110,6 @@ function contractFromArkContract(encoded, options = {}) {
110
110
  params,
111
111
  state: options.state || "active",
112
112
  createdAt: Date.now(),
113
- expiresAt: options.expiresAt,
114
113
  metadata: options.metadata,
115
114
  };
116
115
  }
@@ -136,7 +135,6 @@ function contractFromArkContractWithAddress(encoded, serverPubKey, addressPrefix
136
135
  address: vtxoScript.address(addressPrefix, serverPubKey).encode(),
137
136
  state: options.state || "active",
138
137
  createdAt: Date.now(),
139
- expiresAt: options.expiresAt,
140
138
  metadata: options.metadata,
141
139
  };
142
140
  }
@@ -14,7 +14,7 @@ const DEFAULT_PAGE_SIZE = 500;
14
14
  * - Create and persist contracts
15
15
  * - Query stored contracts (optionally with their virtual outputs)
16
16
  * - Provide spendable path selection for a contract
17
- * - Emit contract-related events (virtual output received/spent/expired, connection reset)
17
+ * - Emit contract-related events (virtual output received/spent, connection reset)
18
18
  *
19
19
  * Notes:
20
20
  * - Implementations typically start watching automatically during initialization
@@ -25,8 +25,6 @@ const DEFAULT_PAGE_SIZE = 500;
25
25
  * const manager = await ContractManager.create({
26
26
  * indexerProvider: wallet.indexerProvider,
27
27
  * contractRepository: wallet.contractRepository,
28
- * walletRepository: wallet.walletRepository,
29
- * getDefaultAddress: () => wallet.getAddress(),
30
28
  * });
31
29
  *
32
30
  * // Create a new VHTLC contract
@@ -86,29 +84,16 @@ class ContractManager {
86
84
  if (this.initialized) {
87
85
  return;
88
86
  }
89
- // Load persisted contracts
87
+ // Register persisted contracts with the watcher BEFORE the first
88
+ // sync. `addContract` seeds `lastKnownVtxos` from the repo without
89
+ // starting to poll, so it's cheap, and it populates
90
+ // `getWatchedContracts()` so the sync below can scope itself to the
91
+ // real watched set instead of every contract ever persisted.
90
92
  const contracts = await this.config.contractRepository.getContracts();
91
- // Delta-sync: fetch only virtual outputs that changed since the last cursor,
92
- // falling back to a full bootstrap for scripts seen for the first time.
93
- await this.deltaSyncContracts(contracts);
94
- // Reconcile the pending frontier: fetch all not-yet-finalized virtual outputs
95
- // to catch any that the delta window may have missed.
96
- if (contracts.length > 0) {
97
- await this.reconcilePendingFrontier(contracts);
98
- }
99
- // add all contracts to the watcher
100
- const now = Date.now();
101
93
  for (const contract of contracts) {
102
- // Check for expired contracts and mark as inactive
103
- if (contract.state === "active" &&
104
- contract.expiresAt &&
105
- contract.expiresAt <= now) {
106
- contract.state = "inactive";
107
- await this.config.contractRepository.saveContract(contract);
108
- }
109
- // Add to watcher
110
94
  await this.watcher.addContract(contract);
111
95
  }
96
+ await this.reconcileWatched();
112
97
  this.initialized = true;
113
98
  // Start watching automatically
114
99
  this.stopWatcherFn = await this.watcher.startWatching((event) => {
@@ -117,6 +102,23 @@ class ContractManager {
117
102
  });
118
103
  });
119
104
  }
105
+ /**
106
+ * Delta-sync the full watched set and reconcile the pending frontier.
107
+ *
108
+ * Shared recovery path used on initial boot and after a subscription
109
+ * reconnect. `syncContracts({})` scopes to the current watched set
110
+ * (see {@link ContractWatcher.getWatchedContracts}), uses the
111
+ * cursor-derived delta window, and advances the cursor on success.
112
+ * `reconcilePendingFrontier` catches not-yet-finalized virtual
113
+ * outputs that could sit outside any delta window.
114
+ */
115
+ async reconcileWatched() {
116
+ await this.syncContracts({});
117
+ const watched = this.watcher.getWatchedContracts();
118
+ if (watched.length > 0) {
119
+ await this.reconcilePendingFrontier(watched);
120
+ }
121
+ }
120
122
  /**
121
123
  * Create and register a new contract.
122
124
  *
@@ -161,15 +163,7 @@ class ContractManager {
161
163
  // Persist
162
164
  await this.config.contractRepository.saveContract(contract);
163
165
  // fetch all virtual outputs (including spent/swept) for this contract
164
- const requestStartedAt = Date.now();
165
- await this.fetchContractVxosFromIndexer([contract], true);
166
- // Advance the sync cursor so that the watcher's vtxo_received
167
- // event (triggered by addContract below) doesn't re-bootstrap
168
- // the same script via deltaSyncContracts.
169
- const cutoff = (0, syncCursors_1.cursorCutoff)(requestStartedAt);
170
- await (0, syncCursors_1.advanceSyncCursors)(this.config.walletRepository, {
171
- [contract.script]: cutoff,
172
- });
166
+ await this.fetchContractVxosFromIndexer([contract]);
173
167
  // Add to watcher
174
168
  await this.watcher.addContract(contract);
175
169
  return contract;
@@ -195,12 +189,26 @@ class ContractManager {
195
189
  }
196
190
  async getContractsWithVtxos(filter, pageSize) {
197
191
  const contracts = await this.getContracts(filter);
198
- const vtxos = await this.getVtxosForContracts(contracts, pageSize);
192
+ await this.syncContracts({ contracts, pageSize });
193
+ const vtxos = await this.getVtxosForContracts(contracts);
199
194
  return contracts.map((contract) => ({
200
195
  contract,
201
- vtxos: vtxos.get(contract.script) ?? [],
196
+ vtxos: vtxos.filter((vtxo) => vtxo.contractScript === contract.script),
202
197
  }));
203
198
  }
199
+ async annotateVtxos(vtxos) {
200
+ if (vtxos.length === 0)
201
+ return [];
202
+ const scripts = Array.from(new Set(vtxos.map((v) => v.script)));
203
+ const byScript = new Map();
204
+ const contracts = await this.config.contractRepository.getContracts({
205
+ script: scripts,
206
+ });
207
+ for (const contract of contracts) {
208
+ byScript.set(contract.script, contract);
209
+ }
210
+ return vtxos.map((vtxo) => (0, utils_1.extendVirtualCoinForContract)(vtxo, byScript));
211
+ }
204
212
  buildContractsDbFilter(filter) {
205
213
  return {
206
214
  script: filter.script,
@@ -341,41 +349,18 @@ class ContractManager {
341
349
  /**
342
350
  * Force refresh virtual outputs from the indexer.
343
351
  *
344
- * Without options, clears all sync cursors and re-fetches every contract.
352
+ * Without options, re-fetches every contract and advances the global cursor.
345
353
  * With options, narrows the refresh to specific scripts and/or a time window.
354
+ * Subset refreshes (scripts filter) intentionally do not advance the cursor.
346
355
  */
347
356
  async refreshVtxos(opts) {
348
- let contracts = await this.config.contractRepository.getContracts();
349
- if (opts?.scripts && opts.scripts.length > 0) {
350
- const scriptSet = new Set(opts.scripts);
351
- contracts = contracts.filter((c) => scriptSet.has(c.script));
352
- }
353
- const syncWindow = opts?.after !== undefined || opts?.before !== undefined
354
- ? {
355
- after: opts.after ?? 0,
356
- before: opts.before ?? Date.now(),
357
- }
357
+ const contracts = opts?.scripts
358
+ ? await this.getContracts({ script: opts.scripts })
358
359
  : undefined;
359
- if (!syncWindow) {
360
- // Full refresh — clear cursors so the next delta sync re-bootstraps.
361
- if (opts?.scripts && opts.scripts.length > 0) {
362
- await (0, syncCursors_1.clearSyncCursors)(this.config.walletRepository, opts.scripts);
363
- }
364
- else {
365
- await (0, syncCursors_1.clearSyncCursors)(this.config.walletRepository);
366
- }
367
- }
368
- const requestStartedAt = Date.now();
369
- const fetched = await this.fetchContractVxosFromIndexer(contracts, true, undefined, syncWindow);
370
- // Persist cursors so subsequent incremental syncs don't re-bootstrap.
371
- const cutoff = (0, syncCursors_1.cursorCutoff)(requestStartedAt);
372
- const cursorUpdates = {};
373
- for (const script of fetched.keys()) {
374
- cursorUpdates[script] = cutoff;
375
- }
376
- if (Object.keys(cursorUpdates).length > 0) {
377
- await (0, syncCursors_1.advanceSyncCursors)(this.config.walletRepository, cursorUpdates);
378
- }
360
+ await this.syncContracts({
361
+ contracts,
362
+ window: { after: opts?.after, before: opts?.before },
363
+ });
379
364
  }
380
365
  /**
381
366
  * Check if currently watching.
@@ -404,76 +389,54 @@ class ContractManager {
404
389
  // Delta-sync only the changed virtual outputs for this contract.
405
390
  case "vtxo_received":
406
391
  case "vtxo_spent":
407
- await this.deltaSyncContracts([event.contract]);
392
+ await this.syncContracts({ contracts: [event.contract] });
408
393
  break;
409
- case "connection_reset": {
410
- // After a reconnect we don't know what we missed — full refetch.
411
- const activeWatchedContracts = this.watcher.getActiveContracts();
412
- await this.fetchContractVxosFromIndexer(activeWatchedContracts, true);
394
+ case "connection_reset":
395
+ // Same recovery path as boot: delta-sync the watched set
396
+ // and reconcile the pending frontier. `advanceSyncCursor`
397
+ // is monotonic so this never rewinds the cursor.
398
+ await this.reconcileWatched();
413
399
  break;
414
- }
415
- case "contract_expired":
416
- // just update DB
417
- await this.config.contractRepository.saveContract(event.contract);
418
400
  }
419
401
  // Forward to all callbacks
420
402
  this.emitEvent(event);
421
403
  }
422
- async getVtxosForContracts(contracts, pageSize) {
423
- if (contracts.length === 0) {
424
- return new Map();
425
- }
426
- return await this.fetchContractVxosFromIndexer(contracts, false, pageSize);
404
+ async getVtxosForContracts(contracts) {
405
+ const res = await Promise.all(contracts.map(({ script, address }) => this.config.walletRepository.getVtxos(address).then((vtxos) => vtxos.map((vtxo) => ({
406
+ ...vtxo,
407
+ contractScript: script,
408
+ })))));
409
+ return res.flat();
427
410
  }
428
411
  /**
429
- * Incrementally sync virtual outputs for the given contracts.
430
- * Uses per-script cursors to fetch only what changed since the last sync.
431
- * Scripts without a cursor are bootstrapped with a full fetch.
412
+ * Sync virtual outputs for the given contracts against the indexer.
413
+ *
414
+ * When `options.contracts` is omitted the sync covers the full
415
+ * watched set (active contracts plus any inactive contracts still
416
+ * holding cached VTXOs) and the global cursor is advanced on
417
+ * success. Passing an explicit subset leaves the cursor alone so a
418
+ * narrow poll can't hide data that other contracts still need to
419
+ * pick up.
432
420
  */
433
- async deltaSyncContracts(contracts, pageSize) {
434
- if (contracts.length === 0)
435
- return new Map();
436
- const cursors = await (0, syncCursors_1.getAllSyncCursors)(this.config.walletRepository);
437
- // Partition into bootstrap (no cursor) and delta (has cursor) groups.
438
- const bootstrap = [];
439
- const delta = [];
440
- for (const c of contracts) {
441
- if (cursors[c.script] !== undefined) {
442
- delta.push(c);
443
- }
444
- else {
445
- bootstrap.push(c);
446
- }
447
- }
448
- const result = new Map();
449
- const cursorUpdates = {};
450
- // Full bootstrap for new scripts.
451
- if (bootstrap.length > 0) {
452
- const requestStartedAt = Date.now();
453
- const fetched = await this.fetchContractVxosFromIndexer(bootstrap, true);
421
+ async syncContracts(options) {
422
+ const cursor = await (0, syncCursors_1.getSyncCursor)(this.config.walletRepository);
423
+ const window = options.window ?? (0, syncCursors_1.computeSyncWindow)(cursor);
424
+ // Advance the global cursor only on full-scope, cursor-derived delta
425
+ // syncs. A caller-supplied window is targeted (e.g. `refreshVtxos`)
426
+ // and must not move the cursor — it may skip data outside its bounds.
427
+ // `<=` lets the bootstrap case (cursor=0, window.after=0) write the
428
+ // migration marker on first boot; otherwise the marker would never
429
+ // be written and every subsequent boot would treat the cursor as
430
+ // legacy and re-bootstrap.
431
+ const mustUpdateCursor = options.contracts === undefined &&
432
+ options.window === undefined &&
433
+ (window.after ?? 0) <= cursor;
434
+ const contracts = options.contracts ?? this.watcher.getWatchedContracts();
435
+ const requestStartedAt = Date.now();
436
+ const result = await this.fetchContractVxosFromIndexer(contracts, options.pageSize, window);
437
+ if (mustUpdateCursor) {
454
438
  const cutoff = (0, syncCursors_1.cursorCutoff)(requestStartedAt);
455
- for (const [script, vtxos] of fetched) {
456
- result.set(script, vtxos);
457
- cursorUpdates[script] = cutoff;
458
- }
459
- }
460
- // Delta sync for scripts with an existing cursor.
461
- if (delta.length > 0) {
462
- // Use the oldest cursor so the shared window covers every script.
463
- const minCursor = Math.min(...delta.map((c) => cursors[c.script]));
464
- const window = (0, syncCursors_1.computeSyncWindow)(minCursor);
465
- if (window) {
466
- const requestStartedAt = Date.now();
467
- const fetched = await this.fetchContractVxosFromIndexer(delta, true, pageSize, window);
468
- const cutoff = (0, syncCursors_1.cursorCutoff)(requestStartedAt);
469
- for (const [script, vtxos] of fetched) {
470
- result.set(script, vtxos);
471
- cursorUpdates[script] = cutoff;
472
- }
473
- }
474
- }
475
- if (Object.keys(cursorUpdates).length > 0) {
476
- await (0, syncCursors_1.advanceSyncCursors)(this.config.walletRepository, cursorUpdates);
439
+ await (0, syncCursors_1.advanceSyncCursor)(this.config.walletRepository, cutoff);
477
440
  }
478
441
  return result;
479
442
  }
@@ -489,21 +452,20 @@ class ContractManager {
489
452
  scripts,
490
453
  pendingOnly: true,
491
454
  });
492
- // Group by contract and upsert.
455
+ // Share the annotation path with external callers so the two entry
456
+ // points can't drift.
457
+ const owned = vtxos.filter((v) => scriptToContract.has(v.script));
458
+ const annotated = await this.annotateVtxos(owned);
493
459
  const byContract = new Map();
494
- for (const vtxo of vtxos) {
495
- if (!vtxo.script)
496
- continue;
460
+ for (const vtxo of annotated) {
497
461
  const contract = scriptToContract.get(vtxo.script);
498
- if (!contract)
499
- continue;
500
462
  let arr = byContract.get(contract.address);
501
463
  if (!arr) {
502
464
  arr = [];
503
465
  byContract.set(contract.address, arr);
504
466
  }
505
467
  arr.push({
506
- ...(0, utils_1.extendVtxoFromContract)(vtxo, contract),
468
+ ...vtxo,
507
469
  contractScript: contract.script,
508
470
  });
509
471
  }
@@ -511,8 +473,8 @@ class ContractManager {
511
473
  await this.config.walletRepository.saveVtxos(addr, contractVtxos);
512
474
  }
513
475
  }
514
- async fetchContractVxosFromIndexer(contracts, includeSpent, pageSize, syncWindow) {
515
- const fetched = await this.fetchContractVtxosBulk(contracts, includeSpent, pageSize, syncWindow);
476
+ async fetchContractVxosFromIndexer(contracts, pageSize, syncWindow) {
477
+ const fetched = await this.fetchContractVtxosBulk(contracts, pageSize, syncWindow);
516
478
  const result = new Map();
517
479
  for (const [contractScript, vtxos] of fetched) {
518
480
  result.set(contractScript, vtxos);
@@ -523,23 +485,17 @@ class ContractManager {
523
485
  }
524
486
  return result;
525
487
  }
526
- async fetchContractVtxosBulk(contracts, includeSpent, pageSize = DEFAULT_PAGE_SIZE, syncWindow) {
488
+ async fetchContractVtxosBulk(contracts, pageSize = DEFAULT_PAGE_SIZE, syncWindow) {
527
489
  if (contracts.length === 0) {
528
490
  return new Map();
529
491
  }
530
- // For a single contract, use the paginated path directly.
531
- if (contracts.length === 1) {
532
- const contract = contracts[0];
533
- const vtxos = await this.fetchContractVtxosPaginated(contract, includeSpent, pageSize, syncWindow);
534
- return new Map([[contract.script, vtxos]]);
535
- }
536
- // For multiple contracts, batch all scripts into a single indexer call
537
- // per page to minimise round-trips. Results are keyed by script so we
538
- // can distribute them back to the correct contract afterwards.
492
+ // Batch all scripts into a single indexer call per page to minimise
493
+ // round-trips. Results are keyed by script so we can distribute them
494
+ // back to the correct contract afterwards. Always fetches the full
495
+ // history (spent/swept included) so the repo is the source of truth.
539
496
  const scriptToContract = new Map(contracts.map((c) => [c.script, c]));
540
497
  const result = new Map(contracts.map((c) => [c.script, []]));
541
498
  const scripts = contracts.map((c) => c.script);
542
- const opts = includeSpent ? {} : { spendableOnly: true };
543
499
  const windowOpts = syncWindow
544
500
  ? {
545
501
  ...(syncWindow.after !== undefined && {
@@ -555,22 +511,20 @@ class ContractManager {
555
511
  while (hasMore) {
556
512
  const { vtxos, page } = await this.config.indexerProvider.getVtxos({
557
513
  scripts,
558
- ...opts,
559
514
  ...windowOpts,
560
515
  pageIndex,
561
516
  pageSize,
562
517
  });
563
- for (const vtxo of vtxos) {
564
- // Match the virtual output back to its contract via the script field
565
- // populated by the indexer.
566
- if (!vtxo.script)
567
- continue;
568
- const contract = scriptToContract.get(vtxo.script);
569
- if (!contract)
570
- continue;
571
- result.get(contract.script).push({
572
- ...(0, utils_1.extendVtxoFromContract)(vtxo, contract),
573
- contractScript: contract.script,
518
+ // Match virtual outputs back to their contract via the script field
519
+ // populated by the indexer, then share the annotation path with
520
+ // external callers via annotateVtxos so the two entry points can't
521
+ // drift.
522
+ const owned = vtxos.filter((v) => scriptToContract.has(v.script));
523
+ const annotated = await this.annotateVtxos(owned);
524
+ for (const vtxo of annotated) {
525
+ result.get(vtxo.script).push({
526
+ ...vtxo,
527
+ contractScript: vtxo.script,
574
528
  });
575
529
  }
576
530
  hasMore = page ? vtxos.length === pageSize : false;
@@ -580,42 +534,6 @@ class ContractManager {
580
534
  }
581
535
  return result;
582
536
  }
583
- async fetchContractVtxosPaginated(contract, includeSpent, pageSize = DEFAULT_PAGE_SIZE, syncWindow) {
584
- const allVtxos = [];
585
- let pageIndex = 0;
586
- let hasMore = true;
587
- const opts = includeSpent ? {} : { spendableOnly: true };
588
- const windowOpts = syncWindow
589
- ? {
590
- ...(syncWindow.after !== undefined && {
591
- after: syncWindow.after,
592
- }),
593
- ...(syncWindow.before !== undefined && {
594
- before: syncWindow.before,
595
- }),
596
- }
597
- : {};
598
- while (hasMore) {
599
- const { vtxos, page } = await this.config.indexerProvider.getVtxos({
600
- scripts: [contract.script],
601
- ...opts,
602
- ...windowOpts,
603
- pageIndex,
604
- pageSize,
605
- });
606
- for (const vtxo of vtxos) {
607
- allVtxos.push({
608
- ...(0, utils_1.extendVtxoFromContract)(vtxo, contract),
609
- contractScript: contract.script,
610
- });
611
- }
612
- hasMore = page ? vtxos.length === pageSize : false;
613
- pageIndex++;
614
- if (hasMore)
615
- await new Promise((r) => setTimeout(r, 500));
616
- }
617
- return allVtxos;
618
- }
619
537
  /**
620
538
  * Dispose of the ContractManager and release all resources.
621
539
  *
@@ -644,13 +562,7 @@ class ContractManager {
644
562
  * ```
645
563
  */
646
564
  [Symbol.dispose]() {
647
- // Stop watching
648
- this.stopWatcherFn?.();
649
- this.stopWatcherFn = undefined;
650
- // Clear callbacks
651
- this.eventCallbacks.clear();
652
- // Mark as uninitialized
653
- this.initialized = false;
565
+ this.dispose();
654
566
  }
655
567
  }
656
568
  exports.ContractManager = ContractManager;