@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
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
|
|
@@ -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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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
|
-
|
|
349
|
-
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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.
|
|
392
|
+
await this.syncContracts({ contracts: [event.contract] });
|
|
408
393
|
break;
|
|
409
|
-
case "connection_reset":
|
|
410
|
-
//
|
|
411
|
-
|
|
412
|
-
|
|
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
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
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
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
//
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const result =
|
|
449
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
...
|
|
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,
|
|
515
|
-
const fetched = await this.fetchContractVtxosBulk(contracts,
|
|
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,
|
|
488
|
+
async fetchContractVtxosBulk(contracts, pageSize = DEFAULT_PAGE_SIZE, syncWindow) {
|
|
527
489
|
if (contracts.length === 0) {
|
|
528
490
|
return new Map();
|
|
529
491
|
}
|
|
530
|
-
//
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
-
|
|
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;
|