@arkade-os/sdk 0.4.17 → 0.4.19
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 -215
- package/dist/cjs/contracts/contractWatcher.js +86 -115
- package/dist/cjs/providers/ark.js +36 -33
- 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 +222 -40
- package/dist/cjs/wallet/wallet.js +149 -211
- 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 -217
- package/dist/esm/contracts/contractWatcher.js +86 -115
- package/dist/esm/providers/ark.js +36 -33
- 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 +222 -40
- package/dist/esm/wallet/wallet.js +152 -214
- 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 -9
- 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 +29 -6
- package/dist/types/wallet/wallet.d.ts +8 -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
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 {
|
|
882
|
-
|
|
883
|
-
|
|
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
|
|
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
|
|
@@ -84,29 +84,16 @@ class ContractManager {
|
|
|
84
84
|
if (this.initialized) {
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
87
|
-
//
|
|
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.
|
|
88
92
|
const contracts = await this.config.contractRepository.getContracts();
|
|
89
|
-
// Delta-sync: fetch only virtual outputs that changed since the last cursor,
|
|
90
|
-
// falling back to a full bootstrap for scripts seen for the first time.
|
|
91
|
-
await this.deltaSyncContracts(contracts, undefined, true);
|
|
92
|
-
// Reconcile the pending frontier: fetch all not-yet-finalized virtual outputs
|
|
93
|
-
// to catch any that the delta window may have missed.
|
|
94
|
-
if (contracts.length > 0) {
|
|
95
|
-
await this.reconcilePendingFrontier(contracts);
|
|
96
|
-
}
|
|
97
|
-
// add all contracts to the watcher
|
|
98
|
-
const now = Date.now();
|
|
99
93
|
for (const contract of contracts) {
|
|
100
|
-
// Check for expired contracts and mark as inactive
|
|
101
|
-
if (contract.state === "active" &&
|
|
102
|
-
contract.expiresAt &&
|
|
103
|
-
contract.expiresAt <= now) {
|
|
104
|
-
contract.state = "inactive";
|
|
105
|
-
await this.config.contractRepository.saveContract(contract);
|
|
106
|
-
}
|
|
107
|
-
// Add to watcher
|
|
108
94
|
await this.watcher.addContract(contract);
|
|
109
95
|
}
|
|
96
|
+
await this.reconcileWatched();
|
|
110
97
|
this.initialized = true;
|
|
111
98
|
// Start watching automatically
|
|
112
99
|
this.stopWatcherFn = await this.watcher.startWatching((event) => {
|
|
@@ -115,6 +102,23 @@ class ContractManager {
|
|
|
115
102
|
});
|
|
116
103
|
});
|
|
117
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
|
+
}
|
|
118
122
|
/**
|
|
119
123
|
* Create and register a new contract.
|
|
120
124
|
*
|
|
@@ -159,15 +163,7 @@ class ContractManager {
|
|
|
159
163
|
// Persist
|
|
160
164
|
await this.config.contractRepository.saveContract(contract);
|
|
161
165
|
// fetch all virtual outputs (including spent/swept) for this contract
|
|
162
|
-
|
|
163
|
-
await this.fetchContractVxosFromIndexer([contract], true);
|
|
164
|
-
// Advance the sync cursor so that the watcher's vtxo_received
|
|
165
|
-
// event (triggered by addContract below) doesn't re-bootstrap
|
|
166
|
-
// the same script via deltaSyncContracts.
|
|
167
|
-
const cutoff = (0, syncCursors_1.cursorCutoff)(requestStartedAt);
|
|
168
|
-
await (0, syncCursors_1.advanceSyncCursors)(this.config.walletRepository, {
|
|
169
|
-
[contract.script]: cutoff,
|
|
170
|
-
});
|
|
166
|
+
await this.fetchContractVxosFromIndexer([contract]);
|
|
171
167
|
// Add to watcher
|
|
172
168
|
await this.watcher.addContract(contract);
|
|
173
169
|
return contract;
|
|
@@ -193,12 +189,26 @@ class ContractManager {
|
|
|
193
189
|
}
|
|
194
190
|
async getContractsWithVtxos(filter, pageSize) {
|
|
195
191
|
const contracts = await this.getContracts(filter);
|
|
196
|
-
|
|
192
|
+
await this.syncContracts({ contracts, pageSize });
|
|
193
|
+
const vtxos = await this.getVtxosForContracts(contracts);
|
|
197
194
|
return contracts.map((contract) => ({
|
|
198
195
|
contract,
|
|
199
|
-
vtxos: vtxos.
|
|
196
|
+
vtxos: vtxos.filter((vtxo) => vtxo.contractScript === contract.script),
|
|
200
197
|
}));
|
|
201
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
|
+
}
|
|
202
212
|
buildContractsDbFilter(filter) {
|
|
203
213
|
return {
|
|
204
214
|
script: filter.script,
|
|
@@ -339,41 +349,18 @@ class ContractManager {
|
|
|
339
349
|
/**
|
|
340
350
|
* Force refresh virtual outputs from the indexer.
|
|
341
351
|
*
|
|
342
|
-
* Without options,
|
|
352
|
+
* Without options, re-fetches every contract and advances the global cursor.
|
|
343
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.
|
|
344
355
|
*/
|
|
345
356
|
async refreshVtxos(opts) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
const scriptSet = new Set(opts.scripts);
|
|
349
|
-
contracts = contracts.filter((c) => scriptSet.has(c.script));
|
|
350
|
-
}
|
|
351
|
-
const syncWindow = opts?.after !== undefined || opts?.before !== undefined
|
|
352
|
-
? {
|
|
353
|
-
after: opts.after ?? 0,
|
|
354
|
-
before: opts.before ?? Date.now(),
|
|
355
|
-
}
|
|
357
|
+
const contracts = opts?.scripts
|
|
358
|
+
? await this.getContracts({ script: opts.scripts })
|
|
356
359
|
: undefined;
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
else {
|
|
363
|
-
await (0, syncCursors_1.clearSyncCursors)(this.config.walletRepository);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
const requestStartedAt = Date.now();
|
|
367
|
-
const fetched = await this.fetchContractVxosFromIndexer(contracts, true, undefined, syncWindow);
|
|
368
|
-
// Persist cursors so subsequent incremental syncs don't re-bootstrap.
|
|
369
|
-
const cutoff = (0, syncCursors_1.cursorCutoff)(requestStartedAt);
|
|
370
|
-
const cursorUpdates = {};
|
|
371
|
-
for (const script of fetched.keys()) {
|
|
372
|
-
cursorUpdates[script] = cutoff;
|
|
373
|
-
}
|
|
374
|
-
if (Object.keys(cursorUpdates).length > 0) {
|
|
375
|
-
await (0, syncCursors_1.advanceSyncCursors)(this.config.walletRepository, cursorUpdates);
|
|
376
|
-
}
|
|
360
|
+
await this.syncContracts({
|
|
361
|
+
contracts,
|
|
362
|
+
window: { after: opts?.after, before: opts?.before },
|
|
363
|
+
});
|
|
377
364
|
}
|
|
378
365
|
/**
|
|
379
366
|
* Check if currently watching.
|
|
@@ -402,94 +389,54 @@ class ContractManager {
|
|
|
402
389
|
// Delta-sync only the changed virtual outputs for this contract.
|
|
403
390
|
case "vtxo_received":
|
|
404
391
|
case "vtxo_spent":
|
|
405
|
-
await this.
|
|
392
|
+
await this.syncContracts({ contracts: [event.contract] });
|
|
406
393
|
break;
|
|
407
|
-
case "connection_reset":
|
|
408
|
-
//
|
|
409
|
-
|
|
410
|
-
|
|
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();
|
|
411
399
|
break;
|
|
412
|
-
}
|
|
413
|
-
case "contract_expired":
|
|
414
|
-
// just update DB
|
|
415
|
-
await this.config.contractRepository.saveContract(event.contract);
|
|
416
400
|
}
|
|
417
401
|
// Forward to all callbacks
|
|
418
402
|
this.emitEvent(event);
|
|
419
403
|
}
|
|
420
|
-
async getVtxosForContracts(contracts
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
// reference so the next call triggers a fresh fetch.
|
|
427
|
-
// TODO: can be removed once we fix the persistence layer (address vs scripts)
|
|
428
|
-
if (this.syncVtxosCallInflight) {
|
|
429
|
-
return this.syncVtxosCallInflight;
|
|
430
|
-
}
|
|
431
|
-
this.syncVtxosCallInflight = this.fetchContractVxosFromIndexer(contracts, true, pageSize).finally(() => {
|
|
432
|
-
this.syncVtxosCallInflight = undefined;
|
|
433
|
-
});
|
|
434
|
-
return this.syncVtxosCallInflight;
|
|
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();
|
|
435
410
|
}
|
|
436
411
|
/**
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
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.
|
|
440
420
|
*/
|
|
441
|
-
async
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
//
|
|
450
|
-
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
delta.push(c);
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
bootstrap.push(c);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
const result = new Map();
|
|
465
|
-
const cursorUpdates = {};
|
|
466
|
-
// Full bootstrap for new scripts.
|
|
467
|
-
if (bootstrap.length > 0) {
|
|
468
|
-
const requestStartedAt = Date.now();
|
|
469
|
-
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) {
|
|
470
438
|
const cutoff = (0, syncCursors_1.cursorCutoff)(requestStartedAt);
|
|
471
|
-
|
|
472
|
-
result.set(script, vtxos);
|
|
473
|
-
cursorUpdates[script] = cutoff;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
// Delta sync for scripts with an existing cursor.
|
|
477
|
-
if (delta.length > 0) {
|
|
478
|
-
// Use the oldest cursor so the shared window covers every script.
|
|
479
|
-
const minCursor = Math.min(...delta.map((c) => cursors[c.script]));
|
|
480
|
-
const window = (0, syncCursors_1.computeSyncWindow)(minCursor);
|
|
481
|
-
if (window) {
|
|
482
|
-
const requestStartedAt = Date.now();
|
|
483
|
-
const fetched = await this.fetchContractVxosFromIndexer(delta, true, pageSize, window);
|
|
484
|
-
const cutoff = (0, syncCursors_1.cursorCutoff)(requestStartedAt);
|
|
485
|
-
for (const [script, vtxos] of fetched) {
|
|
486
|
-
result.set(script, vtxos);
|
|
487
|
-
cursorUpdates[script] = cutoff;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
if (Object.keys(cursorUpdates).length > 0) {
|
|
492
|
-
await (0, syncCursors_1.advanceSyncCursors)(this.config.walletRepository, cursorUpdates);
|
|
439
|
+
await (0, syncCursors_1.advanceSyncCursor)(this.config.walletRepository, cutoff);
|
|
493
440
|
}
|
|
494
441
|
return result;
|
|
495
442
|
}
|
|
@@ -505,21 +452,20 @@ class ContractManager {
|
|
|
505
452
|
scripts,
|
|
506
453
|
pendingOnly: true,
|
|
507
454
|
});
|
|
508
|
-
//
|
|
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);
|
|
509
459
|
const byContract = new Map();
|
|
510
|
-
for (const vtxo of
|
|
511
|
-
if (!vtxo.script)
|
|
512
|
-
continue;
|
|
460
|
+
for (const vtxo of annotated) {
|
|
513
461
|
const contract = scriptToContract.get(vtxo.script);
|
|
514
|
-
if (!contract)
|
|
515
|
-
continue;
|
|
516
462
|
let arr = byContract.get(contract.address);
|
|
517
463
|
if (!arr) {
|
|
518
464
|
arr = [];
|
|
519
465
|
byContract.set(contract.address, arr);
|
|
520
466
|
}
|
|
521
467
|
arr.push({
|
|
522
|
-
...
|
|
468
|
+
...vtxo,
|
|
523
469
|
contractScript: contract.script,
|
|
524
470
|
});
|
|
525
471
|
}
|
|
@@ -527,8 +473,8 @@ class ContractManager {
|
|
|
527
473
|
await this.config.walletRepository.saveVtxos(addr, contractVtxos);
|
|
528
474
|
}
|
|
529
475
|
}
|
|
530
|
-
async fetchContractVxosFromIndexer(contracts,
|
|
531
|
-
const fetched = await this.fetchContractVtxosBulk(contracts,
|
|
476
|
+
async fetchContractVxosFromIndexer(contracts, pageSize, syncWindow) {
|
|
477
|
+
const fetched = await this.fetchContractVtxosBulk(contracts, pageSize, syncWindow);
|
|
532
478
|
const result = new Map();
|
|
533
479
|
for (const [contractScript, vtxos] of fetched) {
|
|
534
480
|
result.set(contractScript, vtxos);
|
|
@@ -539,23 +485,17 @@ class ContractManager {
|
|
|
539
485
|
}
|
|
540
486
|
return result;
|
|
541
487
|
}
|
|
542
|
-
async fetchContractVtxosBulk(contracts,
|
|
488
|
+
async fetchContractVtxosBulk(contracts, pageSize = DEFAULT_PAGE_SIZE, syncWindow) {
|
|
543
489
|
if (contracts.length === 0) {
|
|
544
490
|
return new Map();
|
|
545
491
|
}
|
|
546
|
-
//
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
return new Map([[contract.script, vtxos]]);
|
|
551
|
-
}
|
|
552
|
-
// For multiple contracts, batch all scripts into a single indexer call
|
|
553
|
-
// per page to minimise round-trips. Results are keyed by script so we
|
|
554
|
-
// 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.
|
|
555
496
|
const scriptToContract = new Map(contracts.map((c) => [c.script, c]));
|
|
556
497
|
const result = new Map(contracts.map((c) => [c.script, []]));
|
|
557
498
|
const scripts = contracts.map((c) => c.script);
|
|
558
|
-
const opts = includeSpent ? {} : { spendableOnly: true };
|
|
559
499
|
const windowOpts = syncWindow
|
|
560
500
|
? {
|
|
561
501
|
...(syncWindow.after !== undefined && {
|
|
@@ -571,22 +511,20 @@ class ContractManager {
|
|
|
571
511
|
while (hasMore) {
|
|
572
512
|
const { vtxos, page } = await this.config.indexerProvider.getVtxos({
|
|
573
513
|
scripts,
|
|
574
|
-
...opts,
|
|
575
514
|
...windowOpts,
|
|
576
515
|
pageIndex,
|
|
577
516
|
pageSize,
|
|
578
517
|
});
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
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,
|
|
590
528
|
});
|
|
591
529
|
}
|
|
592
530
|
hasMore = page ? vtxos.length === pageSize : false;
|
|
@@ -596,42 +534,6 @@ class ContractManager {
|
|
|
596
534
|
}
|
|
597
535
|
return result;
|
|
598
536
|
}
|
|
599
|
-
async fetchContractVtxosPaginated(contract, includeSpent, pageSize = DEFAULT_PAGE_SIZE, syncWindow) {
|
|
600
|
-
const allVtxos = [];
|
|
601
|
-
let pageIndex = 0;
|
|
602
|
-
let hasMore = true;
|
|
603
|
-
const opts = includeSpent ? {} : { spendableOnly: true };
|
|
604
|
-
const windowOpts = syncWindow
|
|
605
|
-
? {
|
|
606
|
-
...(syncWindow.after !== undefined && {
|
|
607
|
-
after: syncWindow.after,
|
|
608
|
-
}),
|
|
609
|
-
...(syncWindow.before !== undefined && {
|
|
610
|
-
before: syncWindow.before,
|
|
611
|
-
}),
|
|
612
|
-
}
|
|
613
|
-
: {};
|
|
614
|
-
while (hasMore) {
|
|
615
|
-
const { vtxos, page } = await this.config.indexerProvider.getVtxos({
|
|
616
|
-
scripts: [contract.script],
|
|
617
|
-
...opts,
|
|
618
|
-
...windowOpts,
|
|
619
|
-
pageIndex,
|
|
620
|
-
pageSize,
|
|
621
|
-
});
|
|
622
|
-
for (const vtxo of vtxos) {
|
|
623
|
-
allVtxos.push({
|
|
624
|
-
...(0, utils_1.extendVtxoFromContract)(vtxo, contract),
|
|
625
|
-
contractScript: contract.script,
|
|
626
|
-
});
|
|
627
|
-
}
|
|
628
|
-
hasMore = page ? vtxos.length === pageSize : false;
|
|
629
|
-
pageIndex++;
|
|
630
|
-
if (hasMore)
|
|
631
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
632
|
-
}
|
|
633
|
-
return allVtxos;
|
|
634
|
-
}
|
|
635
537
|
/**
|
|
636
538
|
* Dispose of the ContractManager and release all resources.
|
|
637
539
|
*
|
|
@@ -660,13 +562,7 @@ class ContractManager {
|
|
|
660
562
|
* ```
|
|
661
563
|
*/
|
|
662
564
|
[Symbol.dispose]() {
|
|
663
|
-
|
|
664
|
-
this.stopWatcherFn?.();
|
|
665
|
-
this.stopWatcherFn = undefined;
|
|
666
|
-
// Clear callbacks
|
|
667
|
-
this.eventCallbacks.clear();
|
|
668
|
-
// Mark as uninitialized
|
|
669
|
-
this.initialized = false;
|
|
565
|
+
this.dispose();
|
|
670
566
|
}
|
|
671
567
|
}
|
|
672
568
|
exports.ContractManager = ContractManager;
|