@arkade-os/sdk 0.4.10 → 0.4.11

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.
@@ -2,6 +2,8 @@ import { hex } from "@scure/base";
2
2
  import { ContractWatcher } from './contractWatcher.js';
3
3
  import { contractHandlers } from './handlers/index.js';
4
4
  import { extendVtxoFromContract } from '../wallet/utils.js';
5
+ import { advanceSyncCursors, clearSyncCursors, computeSyncWindow, cursorCutoff, getAllSyncCursors, } from '../utils/syncCursors.js';
6
+ const DEFAULT_PAGE_SIZE = 500;
5
7
  /**
6
8
  * Central manager for contract lifecycle and operations.
7
9
  *
@@ -71,10 +73,14 @@ export class ContractManager {
71
73
  }
72
74
  // Load persisted contracts
73
75
  const contracts = await this.config.contractRepository.getContracts();
74
- // fetch all VTXOs (including spent/swept) for all contracts,
75
- // so the repository has full history for transaction history and balance
76
- // TODO: what if the user has 1k contracts?
77
- await this.fetchContractVxosFromIndexer(contracts, true);
76
+ // Delta-sync: fetch only VTXOs that changed since the last cursor,
77
+ // falling back to a full bootstrap for scripts seen for the first time.
78
+ await this.deltaSyncContracts(contracts);
79
+ // Reconcile the pending frontier: fetch all not-yet-finalized VTXOs
80
+ // to catch any that the delta window may have missed.
81
+ if (contracts.length > 0) {
82
+ await this.reconcilePendingFrontier(contracts);
83
+ }
78
84
  // add all contracts to the watcher
79
85
  const now = Date.now();
80
86
  for (const contract of contracts) {
@@ -91,7 +97,9 @@ export class ContractManager {
91
97
  this.initialized = true;
92
98
  // Start watching automatically
93
99
  this.stopWatcherFn = await this.watcher.startWatching((event) => {
94
- this.handleContractEvent(event);
100
+ this.handleContractEvent(event).catch((error) => {
101
+ console.error("Error handling contract event:", error);
102
+ });
95
103
  });
96
104
  }
97
105
  /**
@@ -162,9 +170,9 @@ export class ContractManager {
162
170
  const dbFilter = this.buildContractsDbFilter(filter ?? {});
163
171
  return await this.config.contractRepository.getContracts(dbFilter);
164
172
  }
165
- async getContractsWithVtxos(filter) {
173
+ async getContractsWithVtxos(filter, pageSize) {
166
174
  const contracts = await this.getContracts(filter);
167
- const vtxos = await this.getVtxosForContracts(contracts);
175
+ const vtxos = await this.getVtxosForContracts(contracts, pageSize);
168
176
  return contracts.map((contract) => ({
169
177
  contract,
170
178
  vtxos: vtxos.get(contract.script) ?? [],
@@ -304,12 +312,43 @@ export class ContractManager {
304
312
  };
305
313
  }
306
314
  /**
307
- * Force a full VTXO refresh from the indexer for all contracts.
308
- * Populates the wallet repository with complete VTXO history.
315
+ * Force a VTXO refresh from the indexer.
316
+ *
317
+ * Without options, clears all sync cursors and re-fetches every contract.
318
+ * With options, narrows the refresh to specific scripts and/or a time window.
309
319
  */
310
- async refreshVtxos() {
311
- const contracts = await this.config.contractRepository.getContracts();
312
- await this.fetchContractVxosFromIndexer(contracts, true);
320
+ async refreshVtxos(opts) {
321
+ let contracts = await this.config.contractRepository.getContracts();
322
+ if (opts?.scripts && opts.scripts.length > 0) {
323
+ const scriptSet = new Set(opts.scripts);
324
+ contracts = contracts.filter((c) => scriptSet.has(c.script));
325
+ }
326
+ const syncWindow = opts?.after !== undefined || opts?.before !== undefined
327
+ ? {
328
+ after: opts.after ?? 0,
329
+ before: opts.before ?? Date.now(),
330
+ }
331
+ : undefined;
332
+ if (!syncWindow) {
333
+ // Full refresh — clear cursors so the next delta sync re-bootstraps.
334
+ if (opts?.scripts && opts.scripts.length > 0) {
335
+ await clearSyncCursors(this.config.walletRepository, opts.scripts);
336
+ }
337
+ else {
338
+ await clearSyncCursors(this.config.walletRepository);
339
+ }
340
+ }
341
+ const requestStartedAt = Date.now();
342
+ const fetched = await this.fetchContractVxosFromIndexer(contracts, true, undefined, syncWindow);
343
+ // Persist cursors so subsequent incremental syncs don't re-bootstrap.
344
+ const cutoff = cursorCutoff(requestStartedAt);
345
+ const cursorUpdates = {};
346
+ for (const script of fetched.keys()) {
347
+ cursorUpdates[script] = cutoff;
348
+ }
349
+ if (Object.keys(cursorUpdates).length > 0) {
350
+ await advanceSyncCursors(this.config.walletRepository, cursorUpdates);
351
+ }
313
352
  }
314
353
  /**
315
354
  * Check if currently watching.
@@ -335,14 +374,13 @@ export class ContractManager {
335
374
  */
336
375
  async handleContractEvent(event) {
337
376
  switch (event.type) {
338
- // Every time there is a VTXO event for a contract, refresh all its VTXOs
377
+ // Delta-sync only the changed VTXOs for this contract.
339
378
  case "vtxo_received":
340
379
  case "vtxo_spent":
341
- await this.fetchContractVxosFromIndexer([event.contract], true);
380
+ await this.deltaSyncContracts([event.contract]);
342
381
  break;
343
382
  case "connection_reset": {
344
- // Refetch all VTXOs (including spent/swept) for all active
345
- // contracts so the repo stays consistent with bootstrap state
383
+ // After a reconnect we don't know what we missed — full refetch.
346
384
  const activeWatchedContracts = this.watcher.getActiveContracts();
347
385
  await this.fetchContractVxosFromIndexer(activeWatchedContracts, true);
348
386
  break;
@@ -354,14 +392,100 @@ export class ContractManager {
354
392
  // Forward to all callbacks
355
393
  this.emitEvent(event);
356
394
  }
357
- async getVtxosForContracts(contracts) {
395
+ async getVtxosForContracts(contracts, pageSize) {
358
396
  if (contracts.length === 0) {
359
397
  return new Map();
360
398
  }
361
- return await this.fetchContractVxosFromIndexer(contracts, false);
399
+ return await this.fetchContractVxosFromIndexer(contracts, false, pageSize);
362
400
  }
363
- async fetchContractVxosFromIndexer(contracts, includeSpent) {
364
- const fetched = await this.fetchContractVtxosBulk(contracts, includeSpent);
401
+ /**
402
+ * Incrementally sync VTXOs for the given contracts.
403
+ * Uses per-script cursors to fetch only what changed since the last sync.
404
+ * Scripts without a cursor are bootstrapped with a full fetch.
405
+ */
406
+ async deltaSyncContracts(contracts, pageSize) {
407
+ if (contracts.length === 0)
408
+ return new Map();
409
+ const cursors = await getAllSyncCursors(this.config.walletRepository);
410
+ // Partition into bootstrap (no cursor) and delta (has cursor) groups.
411
+ const bootstrap = [];
412
+ const delta = [];
413
+ for (const c of contracts) {
414
+ if (cursors[c.script] !== undefined) {
415
+ delta.push(c);
416
+ }
417
+ else {
418
+ bootstrap.push(c);
419
+ }
420
+ }
421
+ const result = new Map();
422
+ const cursorUpdates = {};
423
+ // Full bootstrap for new scripts.
424
+ if (bootstrap.length > 0) {
425
+ const requestStartedAt = Date.now();
426
+ const fetched = await this.fetchContractVxosFromIndexer(bootstrap, true);
427
+ const cutoff = cursorCutoff(requestStartedAt);
428
+ for (const [script, vtxos] of fetched) {
429
+ result.set(script, vtxos);
430
+ cursorUpdates[script] = cutoff;
431
+ }
432
+ }
433
+ // Delta sync for scripts with an existing cursor.
434
+ if (delta.length > 0) {
435
+ // Use the oldest cursor so the shared window covers every script.
436
+ const minCursor = Math.min(...delta.map((c) => cursors[c.script]));
437
+ const window = computeSyncWindow(minCursor);
438
+ if (window) {
439
+ const requestStartedAt = Date.now();
440
+ const fetched = await this.fetchContractVxosFromIndexer(delta, true, pageSize, window);
441
+ const cutoff = cursorCutoff(requestStartedAt);
442
+ for (const [script, vtxos] of fetched) {
443
+ result.set(script, vtxos);
444
+ cursorUpdates[script] = cutoff;
445
+ }
446
+ }
447
+ }
448
+ if (Object.keys(cursorUpdates).length > 0) {
449
+ await advanceSyncCursors(this.config.walletRepository, cursorUpdates);
450
+ }
451
+ return result;
452
+ }
453
+ /**
454
+ * Fetch all pending (not-yet-finalized) VTXOs and upsert them into the
455
+ * repository. This catches VTXOs whose state changed outside the delta
456
+ * window (e.g. a spend that hasn't settled yet).
457
+ */
458
+ async reconcilePendingFrontier(contracts) {
459
+ const scripts = contracts.map((c) => c.script);
460
+ const scriptToContract = new Map(contracts.map((c) => [c.script, c]));
461
+ const { vtxos } = await this.config.indexerProvider.getVtxos({
462
+ scripts,
463
+ pendingOnly: true,
464
+ });
465
+ // Group by contract and upsert.
466
+ const byContract = new Map();
467
+ for (const vtxo of vtxos) {
468
+ if (!vtxo.script)
469
+ continue;
470
+ const contract = scriptToContract.get(vtxo.script);
471
+ if (!contract)
472
+ continue;
473
+ let arr = byContract.get(contract.address);
474
+ if (!arr) {
475
+ arr = [];
476
+ byContract.set(contract.address, arr);
477
+ }
478
+ arr.push({
479
+ ...extendVtxoFromContract(vtxo, contract),
480
+ contractScript: contract.script,
481
+ });
482
+ }
483
+ for (const [addr, contractVtxos] of byContract) {
484
+ await this.config.walletRepository.saveVtxos(addr, contractVtxos);
485
+ }
486
+ }
487
+ async fetchContractVxosFromIndexer(contracts, includeSpent, pageSize, syncWindow) {
488
+ const fetched = await this.fetchContractVtxosBulk(contracts, includeSpent, pageSize, syncWindow);
365
489
  const result = new Map();
366
490
  for (const [contractScript, vtxos] of fetched) {
367
491
  result.set(contractScript, vtxos);
@@ -372,14 +496,14 @@ export class ContractManager {
372
496
  }
373
497
  return result;
374
498
  }
375
- async fetchContractVtxosBulk(contracts, includeSpent) {
499
+ async fetchContractVtxosBulk(contracts, includeSpent, pageSize = DEFAULT_PAGE_SIZE, syncWindow) {
376
500
  if (contracts.length === 0) {
377
501
  return new Map();
378
502
  }
379
503
  // For a single contract, use the paginated path directly.
380
504
  if (contracts.length === 1) {
381
505
  const contract = contracts[0];
382
- const vtxos = await this.fetchContractVtxosPaginated(contract, includeSpent);
506
+ const vtxos = await this.fetchContractVtxosPaginated(contract, includeSpent, pageSize, syncWindow);
383
507
  return new Map([[contract.script, vtxos]]);
384
508
  }
385
509
  // For multiple contracts, batch all scripts into a single indexer call
@@ -388,14 +512,24 @@ export class ContractManager {
388
512
  const scriptToContract = new Map(contracts.map((c) => [c.script, c]));
389
513
  const result = new Map(contracts.map((c) => [c.script, []]));
390
514
  const scripts = contracts.map((c) => c.script);
391
- const pageSize = 100;
392
515
  const opts = includeSpent ? {} : { spendableOnly: true };
516
+ const windowOpts = syncWindow
517
+ ? {
518
+ ...(syncWindow.after !== undefined && {
519
+ after: syncWindow.after,
520
+ }),
521
+ ...(syncWindow.before !== undefined && {
522
+ before: syncWindow.before,
523
+ }),
524
+ }
525
+ : {};
393
526
  let pageIndex = 0;
394
527
  let hasMore = true;
395
528
  while (hasMore) {
396
529
  const { vtxos, page } = await this.config.indexerProvider.getVtxos({
397
530
  scripts,
398
531
  ...opts,
532
+ ...windowOpts,
399
533
  pageIndex,
400
534
  pageSize,
401
535
  });
@@ -414,19 +548,31 @@ export class ContractManager {
414
548
  }
415
549
  hasMore = page ? vtxos.length === pageSize : false;
416
550
  pageIndex++;
551
+ if (hasMore)
552
+ await new Promise((r) => setTimeout(r, 500));
417
553
  }
418
554
  return result;
419
555
  }
420
- async fetchContractVtxosPaginated(contract, includeSpent) {
421
- const pageSize = 100;
556
+ async fetchContractVtxosPaginated(contract, includeSpent, pageSize = DEFAULT_PAGE_SIZE, syncWindow) {
422
557
  const allVtxos = [];
423
558
  let pageIndex = 0;
424
559
  let hasMore = true;
425
560
  const opts = includeSpent ? {} : { spendableOnly: true };
561
+ const windowOpts = syncWindow
562
+ ? {
563
+ ...(syncWindow.after !== undefined && {
564
+ after: syncWindow.after,
565
+ }),
566
+ ...(syncWindow.before !== undefined && {
567
+ before: syncWindow.before,
568
+ }),
569
+ }
570
+ : {};
426
571
  while (hasMore) {
427
572
  const { vtxos, page } = await this.config.indexerProvider.getVtxos({
428
573
  scripts: [contract.script],
429
574
  ...opts,
575
+ ...windowOpts,
430
576
  pageIndex,
431
577
  pageSize,
432
578
  });
@@ -438,6 +584,8 @@ export class ContractManager {
438
584
  }
439
585
  hasMore = page ? vtxos.length === pageSize : false;
440
586
  pageIndex++;
587
+ if (hasMore)
588
+ await new Promise((r) => setTimeout(r, 500));
441
589
  }
442
590
  return allVtxos;
443
591
  }
@@ -251,23 +251,40 @@ export class RestIndexerProvider {
251
251
  return data;
252
252
  }
253
253
  async getVtxos(opts) {
254
+ const hasScripts = (opts?.scripts?.length ?? 0) > 0;
255
+ const hasOutpoints = (opts?.outpoints?.length ?? 0) > 0;
254
256
  // scripts and outpoints are mutually exclusive
255
- if (opts?.scripts && opts?.outpoints) {
257
+ if (hasScripts && hasOutpoints) {
256
258
  throw new Error("scripts and outpoints are mutually exclusive options");
257
259
  }
258
- if (!opts?.scripts && !opts?.outpoints) {
260
+ if (!hasScripts && !hasOutpoints) {
259
261
  throw new Error("Either scripts or outpoints must be provided");
260
262
  }
263
+ const filterCount = [
264
+ opts?.spendableOnly,
265
+ opts?.spentOnly,
266
+ opts?.recoverableOnly,
267
+ ].filter(Boolean).length;
268
+ if (filterCount > 1) {
269
+ throw new Error("spendableOnly, spentOnly, and recoverableOnly are mutually exclusive options");
270
+ }
271
+ if (opts?.after !== undefined &&
272
+ opts?.before !== undefined &&
273
+ opts.after !== 0 &&
274
+ opts.before !== 0 &&
275
+ opts.before <= opts.after) {
276
+ throw new Error("before must be greater than after");
277
+ }
261
278
  let url = `${this.serverUrl}/v1/indexer/vtxos`;
262
279
  const params = new URLSearchParams();
263
280
  // Handle scripts with multi collection format
264
- if (opts?.scripts && opts.scripts.length > 0) {
281
+ if (hasScripts) {
265
282
  opts.scripts.forEach((script) => {
266
283
  params.append("scripts", script);
267
284
  });
268
285
  }
269
286
  // Handle outpoints with multi collection format
270
- if (opts?.outpoints && opts.outpoints.length > 0) {
287
+ if (hasOutpoints) {
271
288
  opts.outpoints.forEach((outpoint) => {
272
289
  params.append("outpoints", `${outpoint.txid}:${outpoint.vout}`);
273
290
  });
@@ -279,6 +296,12 @@ export class RestIndexerProvider {
279
296
  params.append("spentOnly", opts.spentOnly.toString());
280
297
  if (opts.recoverableOnly !== undefined)
281
298
  params.append("recoverableOnly", opts.recoverableOnly.toString());
299
+ if (opts.pendingOnly !== undefined)
300
+ params.append("pendingOnly", opts.pendingOnly.toString());
301
+ if (opts.after !== undefined)
302
+ params.append("after", opts.after.toString());
303
+ if (opts.before !== undefined)
304
+ params.append("before", opts.before.toString());
282
305
  if (opts.pageIndex !== undefined)
283
306
  params.append("page.index", opts.pageIndex.toString());
284
307
  if (opts.pageSize !== undefined)
@@ -0,0 +1,125 @@
1
+ /** Lag behind real-time to avoid racing with indexer writes. */
2
+ export const SAFETY_LAG_MS = 30000;
3
+ /** Overlap window so boundary VTXOs are never missed. */
4
+ export const OVERLAP_MS = 60000;
5
+ /**
6
+ * Per-repository mutex that serializes wallet-state mutations so that
7
+ * concurrent read-modify-write cycles (e.g. advanceSyncCursors racing
8
+ * with clearSyncCursors or setPendingTxFlag) never silently overwrite
9
+ * each other's changes.
10
+ */
11
+ const walletStateLocks = new WeakMap();
12
+ /**
13
+ * Atomically read, mutate, and persist wallet state.
14
+ * All callers that modify wallet state should go through this helper
15
+ * to avoid lost-update races between interleaved async operations.
16
+ */
17
+ export async function updateWalletState(repo, updater) {
18
+ const prev = walletStateLocks.get(repo) ?? Promise.resolve();
19
+ const op = prev.then(async () => {
20
+ const state = (await repo.getWalletState()) ?? {};
21
+ await repo.saveWalletState(updater(state));
22
+ });
23
+ // Store a version that never rejects so the chain doesn't break.
24
+ walletStateLocks.set(repo, op.catch(() => { }));
25
+ return op;
26
+ }
27
+ /**
28
+ * Read the high-water mark for a single script.
29
+ * Returns `undefined` when the script has never been synced (bootstrap case).
30
+ */
31
+ export async function getSyncCursor(repo, script) {
32
+ const state = await repo.getWalletState();
33
+ return state?.settings?.vtxoSyncCursors?.[script];
34
+ }
35
+ /**
36
+ * Read cursors for every previously-synced script.
37
+ */
38
+ export async function getAllSyncCursors(repo) {
39
+ const state = await repo.getWalletState();
40
+ return state?.settings?.vtxoSyncCursors ?? {};
41
+ }
42
+ /**
43
+ * Advance the cursor for one script after a successful delta sync.
44
+ * `cursor` should be the `before` cutoff used in the request.
45
+ */
46
+ export async function advanceSyncCursor(repo, script, cursor) {
47
+ await updateWalletState(repo, (state) => {
48
+ const existing = state.settings?.vtxoSyncCursors ?? {};
49
+ return {
50
+ ...state,
51
+ settings: {
52
+ ...state.settings,
53
+ vtxoSyncCursors: { ...existing, [script]: cursor },
54
+ },
55
+ };
56
+ });
57
+ }
58
+ /**
59
+ * Advance cursors for multiple scripts in a single write.
60
+ */
61
+ export async function advanceSyncCursors(repo, updates) {
62
+ await updateWalletState(repo, (state) => {
63
+ const existing = state.settings?.vtxoSyncCursors ?? {};
64
+ return {
65
+ ...state,
66
+ settings: {
67
+ ...state.settings,
68
+ vtxoSyncCursors: { ...existing, ...updates },
69
+ },
70
+ };
71
+ });
72
+ }
73
+ /**
74
+ * Remove sync cursors, forcing a full re-bootstrap on next sync.
75
+ * When `scripts` is provided, only those cursors are cleared.
76
+ */
77
+ export async function clearSyncCursors(repo, scripts) {
78
+ await updateWalletState(repo, (state) => {
79
+ if (!scripts) {
80
+ const { vtxoSyncCursors: _, ...restSettings } = state.settings ?? {};
81
+ return {
82
+ ...state,
83
+ settings: restSettings,
84
+ };
85
+ }
86
+ const existing = state.settings?.vtxoSyncCursors ?? {};
87
+ const filtered = { ...existing };
88
+ for (const s of scripts)
89
+ delete filtered[s];
90
+ return {
91
+ ...state,
92
+ settings: {
93
+ ...state.settings,
94
+ vtxoSyncCursors: filtered,
95
+ },
96
+ };
97
+ });
98
+ }
99
+ /**
100
+ * Compute the `after` lower-bound for a delta sync query.
101
+ * Returns `undefined` when the script has no cursor (bootstrap needed).
102
+ *
103
+ * No upper bound (`before`) is applied to the query so that freshly
104
+ * created VTXOs are never excluded. The safety lag is applied only
105
+ * when advancing the cursor (see {@link cursorCutoff}).
106
+ */
107
+ export function computeSyncWindow(cursor) {
108
+ if (cursor === undefined)
109
+ return undefined;
110
+ const after = Math.max(0, cursor - OVERLAP_MS);
111
+ return { after };
112
+ }
113
+ /**
114
+ * The safe high-water mark for cursor advancement.
115
+ * Lags behind real-time by {@link SAFETY_LAG_MS} so that VTXOs still
116
+ * being indexed are re-queried on the next sync.
117
+ *
118
+ * When `requestStartedAt` is provided the cutoff is frozen to the
119
+ * request start rather than wall-clock at commit time, preventing
120
+ * long-running paginated fetches from advancing the cursor past the
121
+ * data they actually observed.
122
+ */
123
+ export function cursorCutoff(requestStartedAt) {
124
+ return (requestStartedAt ?? Date.now()) - SAFETY_LAG_MS;
125
+ }
@@ -21,10 +21,13 @@ export class DelegatorManagerImpl {
21
21
  return { delegated: [], failed: [] };
22
22
  }
23
23
  const destinationScript = ArkAddress.decode(destination).pkScript;
24
+ // fetch server and delegator info once, shared across all groups
25
+ const arkInfo = await this.arkInfoProvider.getInfo();
26
+ const delegateInfo = await this.delegatorProvider.getDelegateInfo();
24
27
  // if explicit delegateAt is provided, delegate all vtxos at once without sorting
25
28
  if (delegateAt) {
26
29
  try {
27
- await delegate(this.identity, this.delegatorProvider, this.arkInfoProvider, vtxos, destinationScript, delegateAt);
30
+ await delegate(this.identity, this.delegatorProvider, arkInfo, delegateInfo, vtxos, destinationScript, delegateAt);
28
31
  }
29
32
  catch (error) {
30
33
  return { delegated: [], failed: [{ outpoints: vtxos, error }] };
@@ -51,7 +54,7 @@ export class DelegatorManagerImpl {
51
54
  // if no groups, it means we only need to delegate the recoverable vtxos
52
55
  if (groupByExpiry.size === 0) {
53
56
  try {
54
- await delegate(this.identity, this.delegatorProvider, this.arkInfoProvider, recoverableVtxos, destinationScript, delegateAt);
57
+ await delegate(this.identity, this.delegatorProvider, arkInfo, delegateInfo, recoverableVtxos, destinationScript, delegateAt);
55
58
  }
56
59
  catch (error) {
57
60
  return {
@@ -68,7 +71,7 @@ export class DelegatorManagerImpl {
68
71
  ...recoverableVtxos,
69
72
  ]);
70
73
  const groupsList = Array.from(groupByExpiry.entries());
71
- const result = await Promise.allSettled(groupsList.map(async ([, vtxosGroup]) => delegate(this.identity, this.delegatorProvider, this.arkInfoProvider, vtxosGroup, destinationScript)));
74
+ const result = await Promise.allSettled(groupsList.map(async ([, vtxosGroup]) => delegate(this.identity, this.delegatorProvider, arkInfo, delegateInfo, vtxosGroup, destinationScript)));
72
75
  const delegated = [];
73
76
  const failed = [];
74
77
  for (const [index, resultItem] of result.entries()) {
@@ -90,7 +93,7 @@ export class DelegatorManagerImpl {
90
93
  * should occur. If not provided, defaults to 12 hours before the earliest
91
94
  * expiry time of the provided vtxos.
92
95
  */
93
- async function delegate(identity, delegatorProvider, arkInfoProvider, vtxos, destinationScript, delegateAt) {
96
+ async function delegate(identity, delegatorProvider, arkInfo, delegateInfo, vtxos, destinationScript, delegateAt) {
94
97
  if (vtxos.length === 0) {
95
98
  throw new Error("unable to delegate: no vtxos provided");
96
99
  }
@@ -116,7 +119,7 @@ async function delegate(identity, delegatorProvider, arkInfoProvider, vtxos, des
116
119
  }
117
120
  }
118
121
  }
119
- const { fees, dust, forfeitAddress, network } = await arkInfoProvider.getInfo();
122
+ const { fees, dust, forfeitAddress, network } = arkInfo;
120
123
  const delegateAtSeconds = delegateAt.getTime() / 1000;
121
124
  const estimator = new Estimator({
122
125
  ...fees.intentFee,
@@ -140,7 +143,7 @@ async function delegate(identity, delegatorProvider, arkInfoProvider, vtxos, des
140
143
  }
141
144
  amount += BigInt(coin.value) - BigInt(inputFee.value);
142
145
  }
143
- const { delegatorAddress, pubkey, fee } = await delegatorProvider.getDelegateInfo();
146
+ const { delegatorAddress, pubkey, fee } = delegateInfo;
144
147
  const outputs = [];
145
148
  const delegatorFee = BigInt(Number(fee));
146
149
  if (delegatorFee > 0n) {