@arkade-os/sdk 0.4.8 → 0.4.9

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 (29) hide show
  1. package/dist/cjs/contracts/contractManager.js +59 -11
  2. package/dist/cjs/contracts/contractWatcher.js +21 -2
  3. package/dist/cjs/providers/expoIndexer.js +1 -0
  4. package/dist/cjs/providers/indexer.js +1 -0
  5. package/dist/cjs/utils/transactionHistory.js +2 -1
  6. package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +109 -29
  7. package/dist/cjs/wallet/serviceWorker/wallet.js +22 -0
  8. package/dist/cjs/wallet/vtxo-manager.js +81 -50
  9. package/dist/cjs/wallet/wallet.js +46 -34
  10. package/dist/cjs/worker/messageBus.js +7 -0
  11. package/dist/esm/contracts/contractManager.js +59 -11
  12. package/dist/esm/contracts/contractWatcher.js +21 -2
  13. package/dist/esm/providers/expoIndexer.js +1 -0
  14. package/dist/esm/providers/indexer.js +1 -0
  15. package/dist/esm/utils/transactionHistory.js +2 -1
  16. package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +109 -29
  17. package/dist/esm/wallet/serviceWorker/wallet.js +22 -0
  18. package/dist/esm/wallet/vtxo-manager.js +81 -50
  19. package/dist/esm/wallet/wallet.js +46 -34
  20. package/dist/esm/worker/messageBus.js +7 -0
  21. package/dist/types/contracts/contractManager.d.ts +10 -0
  22. package/dist/types/repositories/serialization.d.ts +1 -0
  23. package/dist/types/utils/transactionHistory.d.ts +1 -1
  24. package/dist/types/wallet/index.d.ts +2 -0
  25. package/dist/types/wallet/serviceWorker/wallet-message-handler.d.ts +23 -6
  26. package/dist/types/wallet/serviceWorker/wallet.d.ts +9 -1
  27. package/dist/types/wallet/vtxo-manager.d.ts +5 -0
  28. package/dist/types/worker/messageBus.d.ts +6 -0
  29. package/package.json +1 -1
@@ -152,7 +152,12 @@ export class VtxoManager {
152
152
  this.knownBoardingUtxos = new Set();
153
153
  this.sweptBoardingUtxos = new Set();
154
154
  this.pollInProgress = false;
155
+ this.disposed = false;
155
156
  this.consecutivePollFailures = 0;
157
+ // Guards against renewal feedback loop: when renewVtxos() settles, the
158
+ // server emits new VTXOs → vtxo_received → renewVtxos() again → infinite loop.
159
+ this.renewalInProgress = false;
160
+ this.lastRenewalTimestamp = 0;
156
161
  // Normalize: prefer settlementConfig, fall back to renewalConfig, default to enabled
157
162
  if (settlementConfig !== undefined) {
158
163
  this.settlementConfig = settlementConfig;
@@ -336,32 +341,43 @@ export class VtxoManager {
336
341
  * ```
337
342
  */
338
343
  async renewVtxos(eventCallback) {
339
- // Get all VTXOs (including recoverable ones)
340
- // Use default threshold to bypass settlementConfig gate (manual API should always work)
341
- const vtxos = await this.getExpiringVtxos(this.settlementConfig !== false &&
342
- this.settlementConfig?.vtxoThreshold !== undefined
343
- ? this.settlementConfig.vtxoThreshold * 1000
344
- : DEFAULT_RENEWAL_CONFIG.thresholdMs);
345
- if (vtxos.length === 0) {
346
- throw new Error("No VTXOs available to renew");
347
- }
348
- const totalAmount = vtxos.reduce((sum, vtxo) => sum + vtxo.value, 0);
349
- // Get dust amount from wallet
350
- const dustAmount = getDustAmount(this.wallet);
351
- // Check if total amount is above dust threshold
352
- if (BigInt(totalAmount) < dustAmount) {
353
- throw new Error(`Total amount ${totalAmount} is below dust threshold ${dustAmount}`);
344
+ if (this.renewalInProgress) {
345
+ throw new Error("Renewal already in progress");
346
+ }
347
+ this.renewalInProgress = true;
348
+ try {
349
+ // Get all VTXOs (including recoverable ones)
350
+ // Use default threshold to bypass settlementConfig gate (manual API should always work)
351
+ const vtxos = await this.getExpiringVtxos(this.settlementConfig !== false &&
352
+ this.settlementConfig?.vtxoThreshold !== undefined
353
+ ? this.settlementConfig.vtxoThreshold * 1000
354
+ : DEFAULT_RENEWAL_CONFIG.thresholdMs);
355
+ if (vtxos.length === 0) {
356
+ throw new Error("No VTXOs available to renew");
357
+ }
358
+ const totalAmount = vtxos.reduce((sum, vtxo) => sum + vtxo.value, 0);
359
+ // Get dust amount from wallet
360
+ const dustAmount = getDustAmount(this.wallet);
361
+ // Check if total amount is above dust threshold
362
+ if (BigInt(totalAmount) < dustAmount) {
363
+ throw new Error(`Total amount ${totalAmount} is below dust threshold ${dustAmount}`);
364
+ }
365
+ const arkAddress = await this.wallet.getAddress();
366
+ const txid = await this.wallet.settle({
367
+ inputs: vtxos,
368
+ outputs: [
369
+ {
370
+ address: arkAddress,
371
+ amount: BigInt(totalAmount),
372
+ },
373
+ ],
374
+ }, eventCallback);
375
+ this.lastRenewalTimestamp = Date.now();
376
+ return txid;
377
+ }
378
+ finally {
379
+ this.renewalInProgress = false;
354
380
  }
355
- const arkAddress = await this.wallet.getAddress();
356
- return this.wallet.settle({
357
- inputs: vtxos,
358
- outputs: [
359
- {
360
- address: arkAddress,
361
- amount: BigInt(totalAmount),
362
- },
363
- ],
364
- }, eventCallback);
365
381
  }
366
382
  // ========== Boarding UTXO Sweep Methods ==========
367
383
  /**
@@ -529,7 +545,11 @@ export class VtxoManager {
529
545
  }
530
546
  // Start polling for boarding UTXOs independently of contract manager
531
547
  // SSE setup. Use a short delay to let the wallet finish construction.
532
- setTimeout(() => this.startBoardingUtxoPoll(), 1000);
548
+ this.startupPollTimeoutId = setTimeout(() => {
549
+ if (this.disposed)
550
+ return;
551
+ this.startBoardingUtxoPoll();
552
+ }, 1000);
533
553
  try {
534
554
  const [delegatorManager, contractManager, destination] = await Promise.all([
535
555
  this.wallet.getDelegatorManager(),
@@ -540,28 +560,33 @@ export class VtxoManager {
540
560
  if (event.type !== "vtxo_received") {
541
561
  return;
542
562
  }
543
- this.renewVtxos().catch((e) => {
544
- if (e instanceof Error) {
545
- if (e.message.includes("No VTXOs available to renew")) {
546
- // Not an error, just no VTXO eligible for renewal.
547
- return;
563
+ const msSinceLastRenewal = Date.now() - this.lastRenewalTimestamp;
564
+ const shouldRenew = !this.renewalInProgress &&
565
+ msSinceLastRenewal >= VtxoManager.RENEWAL_COOLDOWN_MS;
566
+ if (shouldRenew) {
567
+ this.renewVtxos().catch((e) => {
568
+ if (e instanceof Error) {
569
+ if (e.message.includes("No VTXOs available to renew")) {
570
+ // Not an error, just no VTXO eligible for renewal.
571
+ return;
572
+ }
573
+ if (e.message.includes("is below dust threshold")) {
574
+ // Not an error, just below dust threshold.
575
+ // As more VTXOs are received, the threshold will be raised.
576
+ return;
577
+ }
578
+ if (e.message.includes("VTXO_ALREADY_REGISTERED") ||
579
+ e.message.includes("duplicated input")) {
580
+ // VTXO is already being used in a concurrent
581
+ // user-initiated operation. Skip silently — the
582
+ // wallet's tx lock serializes these, but the
583
+ // renewal will retry on the next cycle.
584
+ return;
585
+ }
548
586
  }
549
- if (e.message.includes("is below dust threshold")) {
550
- // Not an error, just below dust threshold.
551
- // As more VTXOs are received, the threshold will be raised.
552
- return;
553
- }
554
- if (e.message.includes("VTXO_ALREADY_REGISTERED") ||
555
- e.message.includes("duplicated input")) {
556
- // VTXO is already being used in a concurrent
557
- // user-initiated operation. Skip silently — the
558
- // wallet's tx lock serializes these, but the
559
- // renewal will retry on the next cycle.
560
- return;
561
- }
562
- }
563
- console.error("Error renewing VTXOs:", e);
564
- });
587
+ console.error("Error renewing VTXOs:", e);
588
+ });
589
+ }
565
590
  delegatorManager
566
591
  ?.delegate(event.vtxos, destination)
567
592
  .catch((e) => {
@@ -601,7 +626,7 @@ export class VtxoManager {
601
626
  this.pollBoardingUtxos();
602
627
  }
603
628
  schedulePoll() {
604
- if (this.settlementConfig === false)
629
+ if (this.disposed || this.settlementConfig === false)
605
630
  return;
606
631
  const delay = this.getNextPollDelay();
607
632
  this.pollTimeoutId = setTimeout(() => this.pollBoardingUtxos(), delay);
@@ -675,8 +700,8 @@ export class VtxoManager {
675
700
  const expired = boardingUtxos.filter((utxo) => hasBoardingTxExpired(utxo, boardingTimelock, chainTipHeight));
676
701
  expiredSet = new Set(expired.map((u) => `${u.txid}:${u.vout}`));
677
702
  }
678
- catch {
679
- return;
703
+ catch (e) {
704
+ throw e instanceof Error ? e : new Error(String(e));
680
705
  }
681
706
  const unsettledUtxos = boardingUtxos.filter((u) => !this.knownBoardingUtxos.has(`${u.txid}:${u.vout}`) &&
682
707
  !expiredSet.has(`${u.txid}:${u.vout}`));
@@ -698,6 +723,11 @@ export class VtxoManager {
698
723
  }
699
724
  async dispose() {
700
725
  this.disposePromise ?? (this.disposePromise = (async () => {
726
+ this.disposed = true;
727
+ if (this.startupPollTimeoutId) {
728
+ clearTimeout(this.startupPollTimeoutId);
729
+ this.startupPollTimeoutId = undefined;
730
+ }
701
731
  if (this.pollTimeoutId) {
702
732
  clearTimeout(this.pollTimeoutId);
703
733
  this.pollTimeoutId = undefined;
@@ -713,3 +743,4 @@ export class VtxoManager {
713
743
  }
714
744
  }
715
745
  VtxoManager.MAX_BACKOFF_MS = 5 * 60 * 1000; // 5 minutes
746
+ VtxoManager.RENEWAL_COOLDOWN_MS = 30000; // 30 seconds
@@ -273,27 +273,33 @@ export class ReadonlyWallet {
273
273
  const scriptMap = await this.getScriptMap();
274
274
  const f = filter ?? { withRecoverable: true, withUnrolled: false };
275
275
  const allExtended = [];
276
- // Query each script separately so we can extend VTXOs with the correct tapscript
277
- for (const [scriptHex, vtxoScript] of scriptMap) {
278
- const response = await this.indexerProvider.getVtxos({
279
- scripts: [scriptHex],
280
- });
281
- let vtxos = response.vtxos.filter(isSpendable);
282
- if (!f.withRecoverable) {
283
- vtxos = vtxos.filter((vtxo) => !isRecoverable(vtxo) && !isExpired(vtxo));
284
- }
285
- if (f.withUnrolled) {
286
- const spentVtxos = response.vtxos.filter((vtxo) => !isSpendable(vtxo));
287
- vtxos.push(...spentVtxos.filter((vtxo) => vtxo.isUnrolled));
276
+ // Batch all scripts into a single indexer call
277
+ const allScripts = [...scriptMap.keys()];
278
+ const response = await this.indexerProvider.getVtxos({
279
+ scripts: allScripts,
280
+ });
281
+ for (const vtxo of response.vtxos) {
282
+ const vtxoScript = vtxo.script
283
+ ? scriptMap.get(vtxo.script)
284
+ : undefined;
285
+ if (!vtxoScript)
286
+ continue;
287
+ if (isSpendable(vtxo)) {
288
+ if (!f.withRecoverable &&
289
+ (isRecoverable(vtxo) || isExpired(vtxo))) {
290
+ continue;
291
+ }
288
292
  }
289
- for (const vtxo of vtxos) {
290
- allExtended.push({
291
- ...vtxo,
292
- forfeitTapLeafScript: vtxoScript.forfeit(),
293
- intentTapLeafScript: vtxoScript.forfeit(),
294
- tapTree: vtxoScript.encode(),
295
- });
293
+ else {
294
+ if (!f.withUnrolled || !vtxo.isUnrolled)
295
+ continue;
296
296
  }
297
+ allExtended.push({
298
+ ...vtxo,
299
+ forfeitTapLeafScript: vtxoScript.forfeit(),
300
+ intentTapLeafScript: vtxoScript.forfeit(),
301
+ tapTree: vtxoScript.encode(),
302
+ });
297
303
  }
298
304
  // Update cache with fresh data
299
305
  await this.walletRepository.saveVtxos(address, allExtended);
@@ -305,7 +311,7 @@ export class ReadonlyWallet {
305
311
  const { boardingTxs, commitmentsToIgnore } = await this.getBoardingTxs();
306
312
  const getTxCreatedAt = (txid) => this.indexerProvider
307
313
  .getVtxos({ outpoints: [{ txid, vout: 0 }] })
308
- .then((res) => res.vtxos[0]?.createdAt.getTime() || 0);
314
+ .then((res) => res.vtxos[0]?.createdAt.getTime());
309
315
  return buildTransactionHistory(response.vtxos, boardingTxs, commitmentsToIgnore, getTxCreatedAt);
310
316
  }
311
317
  async getBoardingTxs() {
@@ -1338,23 +1344,29 @@ export class Wallet extends ReadonlyWallet {
1338
1344
  async finalizePendingTxs(vtxos) {
1339
1345
  const MAX_INPUTS_PER_INTENT = 20;
1340
1346
  if (!vtxos || vtxos.length === 0) {
1341
- // Query per-script so each VTXO is extended with the correct tapscript
1347
+ // Batch all scripts into a single indexer call
1342
1348
  const scriptMap = await this.getScriptMap();
1343
1349
  const allExtended = [];
1344
- for (const [scriptHex, vtxoScript] of scriptMap) {
1345
- const { vtxos: fetchedVtxos } = await this.indexerProvider.getVtxos({
1346
- scripts: [scriptHex],
1347
- });
1348
- const pending = fetchedVtxos.filter((vtxo) => vtxo.virtualStatus.state !== "swept" &&
1349
- vtxo.virtualStatus.state !== "settled");
1350
- for (const vtxo of pending) {
1351
- allExtended.push({
1352
- ...vtxo,
1353
- forfeitTapLeafScript: vtxoScript.forfeit(),
1354
- intentTapLeafScript: vtxoScript.forfeit(),
1355
- tapTree: vtxoScript.encode(),
1356
- });
1350
+ const allScripts = [...scriptMap.keys()];
1351
+ const { vtxos: fetchedVtxos } = await this.indexerProvider.getVtxos({
1352
+ scripts: allScripts,
1353
+ });
1354
+ for (const vtxo of fetchedVtxos) {
1355
+ const vtxoScript = vtxo.script
1356
+ ? scriptMap.get(vtxo.script)
1357
+ : undefined;
1358
+ if (!vtxoScript)
1359
+ continue;
1360
+ if (vtxo.virtualStatus.state === "swept" ||
1361
+ vtxo.virtualStatus.state === "settled") {
1362
+ continue;
1357
1363
  }
1364
+ allExtended.push({
1365
+ ...vtxo,
1366
+ forfeitTapLeafScript: vtxoScript.forfeit(),
1367
+ intentTapLeafScript: vtxoScript.forfeit(),
1368
+ tapTree: vtxoScript.encode(),
1369
+ });
1358
1370
  }
1359
1371
  if (allExtended.length === 0) {
1360
1372
  return { finalized: [], pending: [] };
@@ -149,8 +149,12 @@ export class MessageBus {
149
149
  identity,
150
150
  arkServerUrl: config.arkServer.url,
151
151
  arkServerPublicKey: config.arkServer.publicKey,
152
+ indexerUrl: config.indexerUrl,
153
+ esploraUrl: config.esploraUrl,
152
154
  storage,
153
155
  delegatorProvider,
156
+ settlementConfig: config.settlementConfig,
157
+ watcherConfig: config.watcherConfig,
154
158
  });
155
159
  return { wallet, arkProvider, readonlyWallet: wallet };
156
160
  }
@@ -160,8 +164,11 @@ export class MessageBus {
160
164
  identity,
161
165
  arkServerUrl: config.arkServer.url,
162
166
  arkServerPublicKey: config.arkServer.publicKey,
167
+ indexerUrl: config.indexerUrl,
168
+ esploraUrl: config.esploraUrl,
163
169
  storage,
164
170
  delegatorProvider,
171
+ watcherConfig: config.watcherConfig,
165
172
  });
166
173
  return { readonlyWallet, arkProvider };
167
174
  }
@@ -102,6 +102,11 @@ export interface IContractManager extends Disposable {
102
102
  * @returns Unsubscribe function
103
103
  */
104
104
  onContractEvent(callback: ContractEventCallback): () => void;
105
+ /**
106
+ * Force a full VTXO refresh from the indexer for all contracts.
107
+ * Populates the wallet repository with complete VTXO history.
108
+ */
109
+ refreshVtxos(): Promise<void>;
105
110
  /**
106
111
  * Whether the underlying watcher is currently active.
107
112
  */
@@ -292,6 +297,11 @@ export declare class ContractManager implements IContractManager {
292
297
  * ```
293
298
  */
294
299
  onContractEvent(callback: ContractEventCallback): () => void;
300
+ /**
301
+ * Force a full VTXO refresh from the indexer for all contracts.
302
+ * Populates the wallet repository with complete VTXO history.
303
+ */
304
+ refreshVtxos(): Promise<void>;
295
305
  /**
296
306
  * Check if currently watching.
297
307
  */
@@ -20,6 +20,7 @@ export declare const serializeVtxo: (v: ExtendedVirtualCoin) => {
20
20
  isUnrolled: boolean;
21
21
  isSpent?: boolean;
22
22
  assets?: import("../wallet").Asset[];
23
+ script?: string;
23
24
  value: number;
24
25
  status: import("../wallet").Status;
25
26
  txid: string;
@@ -11,5 +11,5 @@ type ExtendedArkTransaction = ArkTransaction & {
11
11
  * @param {Set<string>} commitmentsToIgnore - A set of commitment IDs that should be excluded from processing.
12
12
  * @return {ExtendedArkTransaction[]} A sorted array of extended Ark transactions, representing the transaction history.
13
13
  */
14
- export declare function buildTransactionHistory(vtxos: VirtualCoin[], allBoardingTxs: ArkTransaction[], commitmentsToIgnore: Set<string>, getTxCreatedAt?: (txid: string) => Promise<number>): Promise<ExtendedArkTransaction[]>;
14
+ export declare function buildTransactionHistory(vtxos: VirtualCoin[], allBoardingTxs: ArkTransaction[], commitmentsToIgnore: Set<string>, getTxCreatedAt?: (txid: string) => Promise<number | undefined>): Promise<ExtendedArkTransaction[]>;
15
15
  export {};
@@ -220,6 +220,8 @@ export interface VirtualCoin extends Coin {
220
220
  isUnrolled: boolean;
221
221
  isSpent?: boolean;
222
222
  assets?: Asset[];
223
+ /** The scriptPubKey (hex) locking this VTXO, as returned by the indexer. */
224
+ script?: string;
223
225
  }
224
226
  export declare enum TxType {
225
227
  TxSent = "SENT",
@@ -228,6 +228,12 @@ export type ResponseIsContractManagerWatching = ResponseEnvelope & {
228
228
  isWatching: boolean;
229
229
  };
230
230
  };
231
+ export type RequestRefreshVtxos = RequestEnvelope & {
232
+ type: "REFRESH_VTXOS";
233
+ };
234
+ export type ResponseRefreshVtxos = ResponseEnvelope & {
235
+ type: "REFRESH_VTXOS_SUCCESS";
236
+ };
231
237
  export type RequestGetAllSpendingPaths = RequestEnvelope & {
232
238
  type: "GET_ALL_SPENDING_PATHS";
233
239
  payload: {
@@ -432,8 +438,8 @@ export type ResponseSweepExpiredBoardingUtxos = ResponseEnvelope & {
432
438
  txid: string;
433
439
  };
434
440
  };
435
- export type WalletUpdaterRequest = RequestInitWallet | RequestSettle | RequestSendBitcoin | RequestGetAddress | RequestGetBoardingAddress | RequestGetBalance | RequestGetVtxos | RequestGetBoardingUtxos | RequestGetTransactionHistory | RequestGetStatus | RequestClear | RequestReloadWallet | RequestSignTransaction | RequestCreateContract | RequestGetContracts | RequestGetContractsWithVtxos | RequestUpdateContract | RequestDeleteContract | RequestGetSpendablePaths | RequestGetAllSpendingPaths | RequestIsContractManagerWatching | RequestSend | RequestGetAssetDetails | RequestIssue | RequestReissue | RequestBurn | RequestDelegate | RequestGetDelegateInfo | RequestRecoverVtxos | RequestGetRecoverableBalance | RequestGetExpiringVtxos | RequestRenewVtxos | RequestGetExpiredBoardingUtxos | RequestSweepExpiredBoardingUtxos;
436
- export type WalletUpdaterResponse = ResponseEnvelope & (ResponseInitWallet | ResponseSettle | ResponseSettleEvent | ResponseSendBitcoin | ResponseGetAddress | ResponseGetBoardingAddress | ResponseGetBalance | ResponseGetVtxos | ResponseGetBoardingUtxos | ResponseGetTransactionHistory | ResponseGetStatus | ResponseClear | ResponseReloadWallet | ResponseUtxoUpdate | ResponseVtxoUpdate | ResponseSignTransaction | ResponseCreateContract | ResponseGetContracts | ResponseGetContractsWithVtxos | ResponseUpdateContract | ResponseDeleteContract | ResponseGetSpendablePaths | ResponseGetAllSpendingPaths | ResponseIsContractManagerWatching | ResponseContractEvent | ResponseSend | ResponseGetAssetDetails | ResponseIssue | ResponseReissue | ResponseBurn | ResponseDelegate | ResponseGetDelegateInfo | ResponseRecoverVtxos | ResponseRecoverVtxosEvent | ResponseGetRecoverableBalance | ResponseGetExpiringVtxos | ResponseRenewVtxos | ResponseRenewVtxosEvent | ResponseGetExpiredBoardingUtxos | ResponseSweepExpiredBoardingUtxos);
441
+ export type WalletUpdaterRequest = RequestInitWallet | RequestSettle | RequestSendBitcoin | RequestGetAddress | RequestGetBoardingAddress | RequestGetBalance | RequestGetVtxos | RequestGetBoardingUtxos | RequestGetTransactionHistory | RequestGetStatus | RequestClear | RequestReloadWallet | RequestSignTransaction | RequestCreateContract | RequestGetContracts | RequestGetContractsWithVtxos | RequestUpdateContract | RequestDeleteContract | RequestGetSpendablePaths | RequestGetAllSpendingPaths | RequestIsContractManagerWatching | RequestRefreshVtxos | RequestSend | RequestGetAssetDetails | RequestIssue | RequestReissue | RequestBurn | RequestDelegate | RequestGetDelegateInfo | RequestRecoverVtxos | RequestGetRecoverableBalance | RequestGetExpiringVtxos | RequestRenewVtxos | RequestGetExpiredBoardingUtxos | RequestSweepExpiredBoardingUtxos;
442
+ export type WalletUpdaterResponse = ResponseEnvelope & (ResponseInitWallet | ResponseSettle | ResponseSettleEvent | ResponseSendBitcoin | ResponseGetAddress | ResponseGetBoardingAddress | ResponseGetBalance | ResponseGetVtxos | ResponseGetBoardingUtxos | ResponseGetTransactionHistory | ResponseGetStatus | ResponseClear | ResponseReloadWallet | ResponseUtxoUpdate | ResponseVtxoUpdate | ResponseSignTransaction | ResponseCreateContract | ResponseGetContracts | ResponseGetContractsWithVtxos | ResponseUpdateContract | ResponseDeleteContract | ResponseGetSpendablePaths | ResponseGetAllSpendingPaths | ResponseIsContractManagerWatching | ResponseRefreshVtxos | ResponseContractEvent | ResponseSend | ResponseGetAssetDetails | ResponseIssue | ResponseReissue | ResponseBurn | ResponseDelegate | ResponseGetDelegateInfo | ResponseRecoverVtxos | ResponseRecoverVtxosEvent | ResponseGetRecoverableBalance | ResponseGetExpiringVtxos | ResponseRenewVtxos | ResponseRenewVtxosEvent | ResponseGetExpiredBoardingUtxos | ResponseSweepExpiredBoardingUtxos);
437
443
  export declare class WalletMessageHandler implements MessageHandler<WalletUpdaterRequest, WalletUpdaterResponse> {
438
444
  readonly messageTag: string;
439
445
  private wallet;
@@ -463,19 +469,30 @@ export declare class WalletMessageHandler implements MessageHandler<WalletUpdate
463
469
  private handleGetBalance;
464
470
  private getAllBoardingUtxos;
465
471
  /**
466
- * Get spendable vtxos for the current wallet address
472
+ * Get spendable vtxos from the repository
467
473
  */
468
474
  private getSpendableVtxos;
475
+ private onWalletInitialized;
469
476
  /**
470
- * Get swept vtxos for the current wallet address
477
+ * Force a full VTXO refresh from the indexer, then re-run bootstrap.
478
+ * Used by RELOAD_WALLET to ensure fresh data.
471
479
  */
472
- private getSweptVtxos;
473
- private onWalletInitialized;
480
+ private reloadWallet;
474
481
  private handleSettle;
475
482
  private handleSendBitcoin;
476
483
  private handleSignTransaction;
477
484
  private handleDelegate;
478
485
  private handleGetVtxos;
479
486
  private clear;
487
+ /**
488
+ * Read all VTXOs from the repository, aggregated across all contract
489
+ * addresses and the wallet's primary address, with deduplication.
490
+ */
491
+ private getVtxosFromRepo;
492
+ /**
493
+ * Build transaction history from cached VTXOs without hitting the indexer.
494
+ * Falls back to indexer only for uncached transaction timestamps.
495
+ */
496
+ private buildTransactionHistoryFromCache;
480
497
  private ensureContractEventBroadcasting;
481
498
  }
@@ -6,7 +6,8 @@ import { ContractRepository } from "../../repositories/contractRepository";
6
6
  import { RequestInitWallet, ResponseGetStatus, WalletUpdaterRequest, WalletUpdaterResponse } from "./wallet-message-handler";
7
7
  import type { IContractManager } from "../../contracts/contractManager";
8
8
  import type { IDelegatorManager } from "../delegator";
9
- import type { IVtxoManager } from "../vtxo-manager";
9
+ import type { IVtxoManager, SettlementConfig } from "../vtxo-manager";
10
+ import type { ContractWatcherConfig } from "../../contracts/contractWatcher";
10
11
  type PrivateKeyIdentity = Identity & {
11
12
  toHex(): string;
12
13
  };
@@ -45,12 +46,15 @@ type PrivateKeyIdentity = Identity & {
45
46
  interface ServiceWorkerWalletOptions {
46
47
  arkServerPublicKey?: string;
47
48
  arkServerUrl: string;
49
+ indexerUrl?: string;
48
50
  esploraUrl?: string;
49
51
  storage?: StorageConfig;
50
52
  identity: ReadonlyIdentity | Identity;
51
53
  delegatorUrl?: string;
52
54
  walletUpdaterTag?: string;
53
55
  messageBusTimeoutMs?: number;
56
+ settlementConfig?: SettlementConfig | false;
57
+ watcherConfig?: Partial<Omit<ContractWatcherConfig, "indexerProvider">>;
54
58
  }
55
59
  export type ServiceWorkerWalletCreateOptions = ServiceWorkerWalletOptions & {
56
60
  serviceWorker: ServiceWorker;
@@ -70,7 +74,11 @@ type MessageBusInitConfig = {
70
74
  publicKey?: string;
71
75
  };
72
76
  delegatorUrl?: string;
77
+ indexerUrl?: string;
78
+ esploraUrl?: string;
73
79
  timeoutMs?: number;
80
+ settlementConfig?: SettlementConfig | false;
81
+ watcherConfig?: Partial<Omit<ContractWatcherConfig, "indexerProvider">>;
74
82
  };
75
83
  export declare class ServiceWorkerReadonlyWallet implements IReadonlyWallet {
76
84
  readonly serviceWorker: ServiceWorker;
@@ -171,8 +171,13 @@ export declare class VtxoManager implements AsyncDisposable, IVtxoManager {
171
171
  private knownBoardingUtxos;
172
172
  private sweptBoardingUtxos;
173
173
  private pollInProgress;
174
+ private disposed;
174
175
  private consecutivePollFailures;
176
+ private startupPollTimeoutId?;
175
177
  private static readonly MAX_BACKOFF_MS;
178
+ private renewalInProgress;
179
+ private lastRenewalTimestamp;
180
+ private static readonly RENEWAL_COOLDOWN_MS;
176
181
  constructor(wallet: IWallet,
177
182
  /** @deprecated Use settlementConfig instead */
178
183
  renewalConfig?: RenewalConfig | undefined, settlementConfig?: SettlementConfig | false);
@@ -1,5 +1,7 @@
1
1
  import { ArkProvider } from "../providers/ark";
2
2
  import { ReadonlyWallet, Wallet } from "../wallet/wallet";
3
+ import type { SettlementConfig } from "../wallet/vtxo-manager";
4
+ import type { ContractWatcherConfig } from "../contracts/contractWatcher";
3
5
  import { ContractRepository, WalletRepository } from "../repositories";
4
6
  export type RequestEnvelope = {
5
7
  tag: string;
@@ -70,6 +72,10 @@ type Initialize = {
70
72
  publicKey?: string;
71
73
  };
72
74
  delegatorUrl?: string;
75
+ indexerUrl?: string;
76
+ esploraUrl?: string;
77
+ settlementConfig?: SettlementConfig | false;
78
+ watcherConfig?: Partial<Omit<ContractWatcherConfig, "indexerProvider">>;
73
79
  };
74
80
  };
75
81
  export declare class MessageBus {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkade-os/sdk",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "Bitcoin wallet SDK with Taproot and Ark integration",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",