@arkade-os/sdk 0.4.48 → 0.4.49-rc.0

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 (79) hide show
  1. package/dist/adapters/expo.cjs +5 -5
  2. package/dist/adapters/expo.d.cts +2 -2
  3. package/dist/adapters/expo.d.ts +2 -2
  4. package/dist/adapters/expo.js +3 -3
  5. package/dist/adapters/indexedDB.cjs +5 -5
  6. package/dist/adapters/indexedDB.js +4 -4
  7. package/dist/{ark-CjE0KkGp.d.cts → ark-Bz4VxTY7.d.cts} +85 -23
  8. package/dist/{ark-CjE0KkGp.d.ts → ark-Bz4VxTY7.d.ts} +85 -23
  9. package/dist/{asyncStorageTaskQueue-CeLZhZFU.d.cts → asyncStorageTaskQueue-BgQaK3El.d.cts} +2 -2
  10. package/dist/{asyncStorageTaskQueue-D9SOPMPC.d.ts → asyncStorageTaskQueue-UIyDWZ_d.d.ts} +2 -2
  11. package/dist/{chunk-G6J6AEMH.js → chunk-27DACHF6.js} +4 -4
  12. package/dist/{chunk-G6J6AEMH.js.map → chunk-27DACHF6.js.map} +1 -1
  13. package/dist/{chunk-XFMGIFBL.js → chunk-COB6VP4M.js} +4 -4
  14. package/dist/{chunk-XFMGIFBL.js.map → chunk-COB6VP4M.js.map} +1 -1
  15. package/dist/{chunk-7TXSPRN5.cjs → chunk-E7HB3GKK.cjs} +31 -2
  16. package/dist/chunk-E7HB3GKK.cjs.map +1 -0
  17. package/dist/{chunk-ENDNQPZA.cjs → chunk-EEYMJVF6.cjs} +17 -17
  18. package/dist/{chunk-ENDNQPZA.cjs.map → chunk-EEYMJVF6.cjs.map} +1 -1
  19. package/dist/{chunk-ZDDBTFVO.js → chunk-EFNLTS6Q.js} +29 -3
  20. package/dist/chunk-EFNLTS6Q.js.map +1 -0
  21. package/dist/{chunk-BHHLRZCS.js → chunk-ITT2S4O3.js} +4 -4
  22. package/dist/chunk-ITT2S4O3.js.map +1 -0
  23. package/dist/{chunk-5XMVTK5S.cjs → chunk-LJQ2VQBE.cjs} +211 -230
  24. package/dist/chunk-LJQ2VQBE.cjs.map +1 -0
  25. package/dist/{chunk-4VVTRPH3.js → chunk-MBSYQ3WX.js} +183 -201
  26. package/dist/chunk-MBSYQ3WX.js.map +1 -0
  27. package/dist/{chunk-ENZ4YPR2.cjs → chunk-R3LGVLKP.cjs} +491 -418
  28. package/dist/chunk-R3LGVLKP.cjs.map +1 -0
  29. package/dist/{chunk-EB3ALGTM.cjs → chunk-RNNCQHAI.cjs} +13 -13
  30. package/dist/chunk-RNNCQHAI.cjs.map +1 -0
  31. package/dist/{chunk-NYR5H6GR.js → chunk-T2IVKSZP.js} +154 -81
  32. package/dist/chunk-T2IVKSZP.js.map +1 -0
  33. package/dist/{chunk-SYDANBJI.cjs → chunk-XWZY6NHJ.cjs} +9 -9
  34. package/dist/{chunk-SYDANBJI.cjs.map → chunk-XWZY6NHJ.cjs.map} +1 -1
  35. package/dist/contracts/handlers/index.cjs +8 -8
  36. package/dist/contracts/handlers/index.d.cts +3 -3
  37. package/dist/contracts/handlers/index.d.ts +3 -3
  38. package/dist/contracts/handlers/index.js +2 -2
  39. package/dist/{delegate-DhGZk1r2.d.ts → delegate-BoIZ4Sjj.d.ts} +1 -1
  40. package/dist/{delegate-D3dY-2vS.d.cts → delegate-D_7Gh5H4.d.cts} +1 -1
  41. package/dist/{index-DK4_iWYy.d.ts → index-Cu6QYjYT.d.ts} +2 -2
  42. package/dist/{index-D6RQROds.d.cts → index-jAMA6NXc.d.cts} +2 -2
  43. package/dist/index.cjs +181 -181
  44. package/dist/index.d.cts +8 -8
  45. package/dist/index.d.ts +8 -8
  46. package/dist/index.js +4 -4
  47. package/dist/repositories/realm/index.cjs +19 -19
  48. package/dist/repositories/realm/index.d.cts +2 -2
  49. package/dist/repositories/realm/index.d.ts +2 -2
  50. package/dist/repositories/realm/index.js +5 -5
  51. package/dist/repositories/sqlite/index.cjs +18 -18
  52. package/dist/repositories/sqlite/index.d.cts +2 -2
  53. package/dist/repositories/sqlite/index.d.ts +2 -2
  54. package/dist/repositories/sqlite/index.js +5 -5
  55. package/dist/{taskRunner-Brnak0Rc.d.cts → taskRunner-Ck0AouQU.d.cts} +2 -2
  56. package/dist/{taskRunner-B-2MHCYG.d.ts → taskRunner-bs1AE_BL.d.ts} +2 -2
  57. package/dist/wallet/expo/background.cjs +14 -14
  58. package/dist/wallet/expo/background.d.cts +3 -3
  59. package/dist/wallet/expo/background.d.ts +3 -3
  60. package/dist/wallet/expo/background.js +6 -6
  61. package/dist/wallet/expo/index.cjs +14 -14
  62. package/dist/wallet/expo/index.d.cts +4 -4
  63. package/dist/wallet/expo/index.d.ts +4 -4
  64. package/dist/wallet/expo/index.js +5 -5
  65. package/dist/{wallet-B5bwixQa.d.ts → wallet-CJ_8yiIz.d.ts} +2 -2
  66. package/dist/{wallet-DiOrtYic.d.cts → wallet-CkTb2epm.d.cts} +2 -2
  67. package/dist/worker/expo/index.cjs +9 -9
  68. package/dist/worker/expo/index.d.cts +4 -4
  69. package/dist/worker/expo/index.d.ts +4 -4
  70. package/dist/worker/expo/index.js +5 -5
  71. package/package.json +1 -1
  72. package/dist/chunk-4VVTRPH3.js.map +0 -1
  73. package/dist/chunk-5XMVTK5S.cjs.map +0 -1
  74. package/dist/chunk-7TXSPRN5.cjs.map +0 -1
  75. package/dist/chunk-BHHLRZCS.js.map +0 -1
  76. package/dist/chunk-EB3ALGTM.cjs.map +0 -1
  77. package/dist/chunk-ENZ4YPR2.cjs.map +0 -1
  78. package/dist/chunk-NYR5H6GR.js.map +0 -1
  79. package/dist/chunk-ZDDBTFVO.js.map +0 -1
@@ -1,19 +1,19 @@
1
1
  'use strict';
2
2
 
3
- var chunkENDNQPZA_cjs = require('../chunk-ENDNQPZA.cjs');
4
- require('../chunk-EB3ALGTM.cjs');
5
- require('../chunk-7TXSPRN5.cjs');
3
+ var chunkEEYMJVF6_cjs = require('../chunk-EEYMJVF6.cjs');
4
+ require('../chunk-RNNCQHAI.cjs');
5
+ require('../chunk-E7HB3GKK.cjs');
6
6
  require('../chunk-5BLDMQED.cjs');
7
7
 
8
8
 
9
9
 
10
10
  Object.defineProperty(exports, "ExpoArkProvider", {
11
11
  enumerable: true,
12
- get: function () { return chunkENDNQPZA_cjs.ExpoArkProvider; }
12
+ get: function () { return chunkEEYMJVF6_cjs.ExpoArkProvider; }
13
13
  });
14
14
  Object.defineProperty(exports, "ExpoIndexerProvider", {
15
15
  enumerable: true,
16
- get: function () { return chunkENDNQPZA_cjs.ExpoIndexerProvider; }
16
+ get: function () { return chunkEEYMJVF6_cjs.ExpoIndexerProvider; }
17
17
  });
18
18
  //# sourceMappingURL=expo.cjs.map
19
19
  //# sourceMappingURL=expo.cjs.map
@@ -1,5 +1,5 @@
1
- import { k as RestArkProvider, i as SettlementEvent, T as TxNotification, l as RestIndexerProvider, m as SubscriptionResponse } from '../ark-CjE0KkGp.cjs';
2
- export { n as ArkProvider, o as IndexerProvider } from '../ark-CjE0KkGp.cjs';
1
+ import { k as RestArkProvider, i as SettlementEvent, T as TxNotification, l as RestIndexerProvider, m as SubscriptionResponse } from '../ark-Bz4VxTY7.cjs';
2
+ export { n as ArkProvider, o as IndexerProvider } from '../ark-Bz4VxTY7.cjs';
3
3
  import '@scure/btc-signer/transaction.js';
4
4
  import '@scure/btc-signer/utils.js';
5
5
  import '@scure/btc-signer/psbt.js';
@@ -1,5 +1,5 @@
1
- import { k as RestArkProvider, i as SettlementEvent, T as TxNotification, l as RestIndexerProvider, m as SubscriptionResponse } from '../ark-CjE0KkGp.js';
2
- export { n as ArkProvider, o as IndexerProvider } from '../ark-CjE0KkGp.js';
1
+ import { k as RestArkProvider, i as SettlementEvent, T as TxNotification, l as RestIndexerProvider, m as SubscriptionResponse } from '../ark-Bz4VxTY7.js';
2
+ export { n as ArkProvider, o as IndexerProvider } from '../ark-Bz4VxTY7.js';
3
3
  import '@scure/btc-signer/transaction.js';
4
4
  import '@scure/btc-signer/utils.js';
5
5
  import '@scure/btc-signer/psbt.js';
@@ -1,6 +1,6 @@
1
- export { ExpoArkProvider, ExpoIndexerProvider } from '../chunk-XFMGIFBL.js';
2
- import '../chunk-BHHLRZCS.js';
3
- import '../chunk-ZDDBTFVO.js';
1
+ export { ExpoArkProvider, ExpoIndexerProvider } from '../chunk-COB6VP4M.js';
2
+ import '../chunk-ITT2S4O3.js';
3
+ import '../chunk-EFNLTS6Q.js';
4
4
  import '../chunk-NSBPE2FW.js';
5
5
  //# sourceMappingURL=expo.js.map
6
6
  //# sourceMappingURL=expo.js.map
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- var chunkENZ4YPR2_cjs = require('../chunk-ENZ4YPR2.cjs');
4
- require('../chunk-EB3ALGTM.cjs');
5
- require('../chunk-5XMVTK5S.cjs');
6
- require('../chunk-7TXSPRN5.cjs');
3
+ var chunkR3LGVLKP_cjs = require('../chunk-R3LGVLKP.cjs');
4
+ require('../chunk-RNNCQHAI.cjs');
5
+ require('../chunk-LJQ2VQBE.cjs');
6
+ require('../chunk-E7HB3GKK.cjs');
7
7
  require('../chunk-5BLDMQED.cjs');
8
8
 
9
9
  // src/storage/indexedDB.ts
@@ -11,7 +11,7 @@ var IndexedDBStorageAdapter = class {
11
11
  dbName;
12
12
  version;
13
13
  db = null;
14
- constructor(dbName, version = chunkENZ4YPR2_cjs.DB_VERSION) {
14
+ constructor(dbName, version = chunkR3LGVLKP_cjs.DB_VERSION) {
15
15
  this.dbName = dbName;
16
16
  this.version = version;
17
17
  }
@@ -1,7 +1,7 @@
1
- import { DB_VERSION } from '../chunk-NYR5H6GR.js';
2
- import '../chunk-BHHLRZCS.js';
3
- import '../chunk-4VVTRPH3.js';
4
- import '../chunk-ZDDBTFVO.js';
1
+ import { DB_VERSION } from '../chunk-T2IVKSZP.js';
2
+ import '../chunk-ITT2S4O3.js';
3
+ import '../chunk-MBSYQ3WX.js';
4
+ import '../chunk-EFNLTS6Q.js';
5
5
  import '../chunk-NSBPE2FW.js';
6
6
 
7
7
  // src/storage/indexedDB.ts
@@ -1912,12 +1912,19 @@ type RefreshVtxosOptions = {
1912
1912
  includeInactive?: boolean;
1913
1913
  };
1914
1914
  /**
1915
- * A single `Discoverable` handler's `discoverAt` rejection, captured during
1916
- * a {@link IContractManager.scanContracts} run instead of aborting the loop.
1915
+ * A single `Discoverable` handler's discovery failure, captured during a
1916
+ * {@link IContractManager.scanContracts} run instead of aborting the loop.
1917
+ *
1918
+ * TODO(next major): rename `index` → `fromIndex` so the pair reads
1919
+ * `fromIndex`/`toIndex`. It stays `index` here only to keep this exported
1920
+ * shape backward-compatible.
1917
1921
  */
1918
1922
  interface HandlerError {
1919
1923
  handler: string;
1924
+ /** The failed index, or the first index of a failed `discoverRange` window. */
1920
1925
  index: number;
1926
+ /** Inclusive end of a failed `discoverRange` window; absent for a single index. */
1927
+ toIndex?: number;
1921
1928
  error: unknown;
1922
1929
  }
1923
1930
  /**
@@ -1941,7 +1948,7 @@ interface ScanResult {
1941
1948
  * when the scan closed a genuine gap.
1942
1949
  */
1943
1950
  truncatedAt?: number;
1944
- /** Per-handler `discoverAt` failures. Non-empty implies `truncatedAt` is set. */
1951
+ /** Per-handler discovery failures. Non-empty implies `truncatedAt` is set. */
1945
1952
  handlerErrors: HandlerError[];
1946
1953
  }
1947
1954
  /**
@@ -1951,11 +1958,13 @@ interface ScanContractsOptions {
1951
1958
  /** Default 20. A non-positive / non-integer value throws. */
1952
1959
  gapLimit?: number;
1953
1960
  /**
1954
- * Number of HD indices probed concurrently per window (default
1955
- * {@link DEFAULT_SCAN_BATCH}). Pure latency knob: the gap loop stays
1956
- * gap-limit bounded and the discovered set is identical regardless of
1957
- * batch size. A non-positive / non-integer value throws. Ignored when
1958
- * `hd` is false (the static pass probes only index 0).
1961
+ * Number of HD indices probed per window (default
1962
+ * {@link DEFAULT_SCAN_BATCH}). The gap loop stays gap-limit bounded and
1963
+ * the discovered set is identical regardless of batch size; the window is
1964
+ * also the unit a batching handler ({@link Discoverable.discoverRange})
1965
+ * collapses into one request, so it doubles as the batch width. A
1966
+ * non-positive / non-integer value throws. Ignored when `hd` is false (the
1967
+ * static pass probes only index 0).
1959
1968
  */
1960
1969
  batchSize?: number;
1961
1970
  /** HD mode → unbounded gap loop guided by the gap counter; false → probe only index 0 (single static pass). */
@@ -2092,11 +2101,12 @@ interface IContractManager extends Disposable {
2092
2101
  * resets the gap counter, so swap discovery keeps the HD window open.
2093
2102
  *
2094
2103
  * Error contract (safety-critical — see spec §4):
2095
- * - A handler's `discoverAt` rejecting is **collected** into
2096
- * `handlerErrors` and makes its index *indeterminate*: it never
2097
- * advances the gap counter, and the scan **stops verifying** there
2098
- * rather than closing a window it never observed close. It still
2099
- * never throws.
2104
+ * - A handler's discovery rejecting is **collected** into `handlerErrors`
2105
+ * and makes its index *indeterminate*: it never advances the gap
2106
+ * counter, and the scan **stops verifying** there rather than closing a
2107
+ * window it never observed close. A batched
2108
+ * {@link Discoverable.discoverRange} failure makes its whole requested
2109
+ * range indeterminate. It still never throws.
2100
2110
  * - A fatal operational error — `materialize()` throwing, or
2101
2111
  * `createContract` rejecting — **propagates** out of `scanContracts`
2102
2112
  * (it invalidates the gap-window signal, so a silent truncation
@@ -2316,21 +2326,27 @@ declare class ContractManager implements IContractManager {
2316
2326
  * Safety-critical invariants (spec §2.C / §4):
2317
2327
  * - `opts.materialize(i)` throwing is structural/fatal: it is NOT
2318
2328
  * wrapped — it propagates and aborts the scan.
2319
- * - A `discoverAt` rejection is collected into `handlerErrors` and makes
2320
- * its index *indeterminate*: it does NOT advance the gap counter, and
2321
- * the scan stops verifying there, reporting `truncatedAt`. Only an index
2329
+ * - A discovery rejection is collected into `handlerErrors` and makes its
2330
+ * index *indeterminate*: it does NOT advance the gap counter, and the
2331
+ * scan stops verifying there, reporting `truncatedAt`. Only an index
2322
2332
  * every handler answered for can be a confirmed miss.
2323
2333
  * - `persistAndWatchContract` rejecting is operational/fatal and
2324
- * propagates (only `discoverAt` is guarded).
2325
- * - Within an index the handler probes run concurrently (independent
2326
- * network reads); their hits are persisted sequentially in
2327
- * `discoverables` order to preserve the first-wins collision tie-break.
2328
- * - Indices are probed `batchSize` at a time (a second concurrency layer
2329
- * over the per-index probes), but each window is CAPPED to
2334
+ * propagates (only the handler calls are guarded).
2335
+ * - A handler exposing {@link Discoverable.discoverRange} is asked for the
2336
+ * whole window in ONE call instead of one per index — the batching that
2337
+ * keeps a large restore from bursting into an operator's rate limiter.
2338
+ * Its failures are therefore range-wide: every index in the window goes
2339
+ * indeterminate and truncation lands on the window's first index. Its
2340
+ * answer must cover every requested index; an incomplete map is treated
2341
+ * as a rejection (see `Discoverable.discoverRange`).
2342
+ * - Handlers are probed concurrently (independent network reads); their
2343
+ * hits are persisted sequentially in `discoverables` order to preserve
2344
+ * the first-wins collision tie-break.
2345
+ * - Indices are probed `batchSize` at a time, but each window is CAPPED to
2330
2346
  * `gapLimit - unused` indices — the most a serial scan could still reach
2331
2347
  * before the gap window is guaranteed to close. So every index probed in
2332
2348
  * a window is one a one-index-at-a-time scan would also reach: nothing is
2333
- * over-scanned, nothing is discarded, and `materialize`/`discoverAt` are
2349
+ * over-scanned, nothing is discarded, and `materialize`/discovery are
2334
2350
  * invoked on exactly the same index set. The window's hits are still
2335
2351
  * processed strictly in ascending index order, so the discovered set,
2336
2352
  * persisted rows, `highestConfirmedUsedIndex`, and `handlerErrors` are
@@ -2339,8 +2355,12 @@ declare class ContractManager implements IContractManager {
2339
2355
  * can surface hits above the failed index that a serial scan would never
2340
2356
  * have reached, so a truncated batched scan discovers a superset — never
2341
2357
  * a subset — of the serial one.
2358
+ * - The whole scan runs inside one coalesced subscription scope, so N
2359
+ * discovered contracts cost ONE `subscribeForScripts` instead of N
2360
+ * growing ones (see {@link ContractWatcher.withCoalescedSubscription}).
2342
2361
  */
2343
2362
  scanContracts(opts: ScanContractsOptions): Promise<ScanResult>;
2363
+ private runScan;
2344
2364
  /**
2345
2365
  * Get contracts with optional filters.
2346
2366
  *
@@ -2770,6 +2790,27 @@ interface DiscoveryDeps {
2770
2790
  */
2771
2791
  interface Discoverable {
2772
2792
  discoverAt(index: number, descriptor: string, deps: DiscoveryDeps): Promise<DiscoveredContract[]>;
2793
+ /**
2794
+ * Optional: answer for a whole scan window in one batched round-trip.
2795
+ * The scanner prefers it over per-index `discoverAt` calls when present,
2796
+ * which is what keeps a 10-index window to 1-2 indexer requests instead
2797
+ * of one per index. Handlers whose source is inherently per-address (e.g.
2798
+ * boarding, on Esplora) implement only `discoverAt`.
2799
+ *
2800
+ * **All-or-nothing per call.** Either resolve with a map covering *every*
2801
+ * requested index (empty array = confirmed miss), or reject — a handler
2802
+ * whose inner chunk fails partway must discard the partial results and
2803
+ * reject, because a missing index would otherwise read as "no funds here"
2804
+ * and let restore close its gap window on a failed request. The scanner
2805
+ * enforces this rather than trusting it: an incomplete map is treated as a
2806
+ * rejection, making the whole requested range indeterminate (hits present
2807
+ * in it are still persisted) and truncating the scan at the range's first
2808
+ * index. Indices that were not requested are ignored.
2809
+ */
2810
+ discoverRange?(entries: readonly {
2811
+ index: number;
2812
+ descriptor: string;
2813
+ }[], deps: DiscoveryDeps): Promise<Map<number, DiscoveredContract[]>>;
2773
2814
  }
2774
2815
  /** Duck-typed guard (mirrors `hasReceiveRotatorFactory`). */
2775
2816
  declare function isDiscoverable(handler: ContractHandler<unknown> | undefined): handler is ContractHandler<unknown> & Discoverable;
@@ -2931,6 +2972,9 @@ declare class ContractWatcher {
2931
2972
  private reconnectAttempts;
2932
2973
  private reconnectTimeoutId?;
2933
2974
  private failsafePollIntervalId?;
2975
+ /** See {@link withCoalescedSubscription}. */
2976
+ private subscriptionBatchDepth;
2977
+ private subscriptionUpdateDeferred;
2934
2978
  /**
2935
2979
  * Create a contract watcher with the given providers and polling settings.
2936
2980
  *
@@ -3025,6 +3069,24 @@ declare class ContractWatcher {
3025
3069
  * Poll specific contracts and emit events for changes.
3026
3070
  */
3027
3071
  private pollContracts;
3072
+ /**
3073
+ * Run `fn` with subscription updates coalesced into a single
3074
+ * `subscribeForScripts` on the way out.
3075
+ *
3076
+ * {@link addContract} re-subscribes eagerly (the watcher may already be
3077
+ * running), and every subscribe posts the *whole* accumulated script list —
3078
+ * so a restore scan that discovers N contracts sends N growing POSTs,
3079
+ * quadratic in script-slots. Inside this scope those updates are only
3080
+ * marked dirty, flushed once on the way out (success and error path alike).
3081
+ *
3082
+ * A contract added inside the scope is therefore not streaming until the
3083
+ * flush. Nothing in the watcher closes that window — the failsafe poll
3084
+ * replays repository state and cannot see VTXOs no one has fetched yet. The
3085
+ * one caller, `scanContracts`, is covered because `Wallet.restore` follows
3086
+ * it with a bulk `refreshVtxos`. A new caller must provide its own
3087
+ * equivalent catch-up, or keep the scope short enough not to need one.
3088
+ */
3089
+ withCoalescedSubscription<T>(fn: () => Promise<T>): Promise<T>;
3028
3090
  private tryUpdateSubscription;
3029
3091
  /**
3030
3092
  * Update the subscription with scripts that should be watched.
@@ -1912,12 +1912,19 @@ type RefreshVtxosOptions = {
1912
1912
  includeInactive?: boolean;
1913
1913
  };
1914
1914
  /**
1915
- * A single `Discoverable` handler's `discoverAt` rejection, captured during
1916
- * a {@link IContractManager.scanContracts} run instead of aborting the loop.
1915
+ * A single `Discoverable` handler's discovery failure, captured during a
1916
+ * {@link IContractManager.scanContracts} run instead of aborting the loop.
1917
+ *
1918
+ * TODO(next major): rename `index` → `fromIndex` so the pair reads
1919
+ * `fromIndex`/`toIndex`. It stays `index` here only to keep this exported
1920
+ * shape backward-compatible.
1917
1921
  */
1918
1922
  interface HandlerError {
1919
1923
  handler: string;
1924
+ /** The failed index, or the first index of a failed `discoverRange` window. */
1920
1925
  index: number;
1926
+ /** Inclusive end of a failed `discoverRange` window; absent for a single index. */
1927
+ toIndex?: number;
1921
1928
  error: unknown;
1922
1929
  }
1923
1930
  /**
@@ -1941,7 +1948,7 @@ interface ScanResult {
1941
1948
  * when the scan closed a genuine gap.
1942
1949
  */
1943
1950
  truncatedAt?: number;
1944
- /** Per-handler `discoverAt` failures. Non-empty implies `truncatedAt` is set. */
1951
+ /** Per-handler discovery failures. Non-empty implies `truncatedAt` is set. */
1945
1952
  handlerErrors: HandlerError[];
1946
1953
  }
1947
1954
  /**
@@ -1951,11 +1958,13 @@ interface ScanContractsOptions {
1951
1958
  /** Default 20. A non-positive / non-integer value throws. */
1952
1959
  gapLimit?: number;
1953
1960
  /**
1954
- * Number of HD indices probed concurrently per window (default
1955
- * {@link DEFAULT_SCAN_BATCH}). Pure latency knob: the gap loop stays
1956
- * gap-limit bounded and the discovered set is identical regardless of
1957
- * batch size. A non-positive / non-integer value throws. Ignored when
1958
- * `hd` is false (the static pass probes only index 0).
1961
+ * Number of HD indices probed per window (default
1962
+ * {@link DEFAULT_SCAN_BATCH}). The gap loop stays gap-limit bounded and
1963
+ * the discovered set is identical regardless of batch size; the window is
1964
+ * also the unit a batching handler ({@link Discoverable.discoverRange})
1965
+ * collapses into one request, so it doubles as the batch width. A
1966
+ * non-positive / non-integer value throws. Ignored when `hd` is false (the
1967
+ * static pass probes only index 0).
1959
1968
  */
1960
1969
  batchSize?: number;
1961
1970
  /** HD mode → unbounded gap loop guided by the gap counter; false → probe only index 0 (single static pass). */
@@ -2092,11 +2101,12 @@ interface IContractManager extends Disposable {
2092
2101
  * resets the gap counter, so swap discovery keeps the HD window open.
2093
2102
  *
2094
2103
  * Error contract (safety-critical — see spec §4):
2095
- * - A handler's `discoverAt` rejecting is **collected** into
2096
- * `handlerErrors` and makes its index *indeterminate*: it never
2097
- * advances the gap counter, and the scan **stops verifying** there
2098
- * rather than closing a window it never observed close. It still
2099
- * never throws.
2104
+ * - A handler's discovery rejecting is **collected** into `handlerErrors`
2105
+ * and makes its index *indeterminate*: it never advances the gap
2106
+ * counter, and the scan **stops verifying** there rather than closing a
2107
+ * window it never observed close. A batched
2108
+ * {@link Discoverable.discoverRange} failure makes its whole requested
2109
+ * range indeterminate. It still never throws.
2100
2110
  * - A fatal operational error — `materialize()` throwing, or
2101
2111
  * `createContract` rejecting — **propagates** out of `scanContracts`
2102
2112
  * (it invalidates the gap-window signal, so a silent truncation
@@ -2316,21 +2326,27 @@ declare class ContractManager implements IContractManager {
2316
2326
  * Safety-critical invariants (spec §2.C / §4):
2317
2327
  * - `opts.materialize(i)` throwing is structural/fatal: it is NOT
2318
2328
  * wrapped — it propagates and aborts the scan.
2319
- * - A `discoverAt` rejection is collected into `handlerErrors` and makes
2320
- * its index *indeterminate*: it does NOT advance the gap counter, and
2321
- * the scan stops verifying there, reporting `truncatedAt`. Only an index
2329
+ * - A discovery rejection is collected into `handlerErrors` and makes its
2330
+ * index *indeterminate*: it does NOT advance the gap counter, and the
2331
+ * scan stops verifying there, reporting `truncatedAt`. Only an index
2322
2332
  * every handler answered for can be a confirmed miss.
2323
2333
  * - `persistAndWatchContract` rejecting is operational/fatal and
2324
- * propagates (only `discoverAt` is guarded).
2325
- * - Within an index the handler probes run concurrently (independent
2326
- * network reads); their hits are persisted sequentially in
2327
- * `discoverables` order to preserve the first-wins collision tie-break.
2328
- * - Indices are probed `batchSize` at a time (a second concurrency layer
2329
- * over the per-index probes), but each window is CAPPED to
2334
+ * propagates (only the handler calls are guarded).
2335
+ * - A handler exposing {@link Discoverable.discoverRange} is asked for the
2336
+ * whole window in ONE call instead of one per index — the batching that
2337
+ * keeps a large restore from bursting into an operator's rate limiter.
2338
+ * Its failures are therefore range-wide: every index in the window goes
2339
+ * indeterminate and truncation lands on the window's first index. Its
2340
+ * answer must cover every requested index; an incomplete map is treated
2341
+ * as a rejection (see `Discoverable.discoverRange`).
2342
+ * - Handlers are probed concurrently (independent network reads); their
2343
+ * hits are persisted sequentially in `discoverables` order to preserve
2344
+ * the first-wins collision tie-break.
2345
+ * - Indices are probed `batchSize` at a time, but each window is CAPPED to
2330
2346
  * `gapLimit - unused` indices — the most a serial scan could still reach
2331
2347
  * before the gap window is guaranteed to close. So every index probed in
2332
2348
  * a window is one a one-index-at-a-time scan would also reach: nothing is
2333
- * over-scanned, nothing is discarded, and `materialize`/`discoverAt` are
2349
+ * over-scanned, nothing is discarded, and `materialize`/discovery are
2334
2350
  * invoked on exactly the same index set. The window's hits are still
2335
2351
  * processed strictly in ascending index order, so the discovered set,
2336
2352
  * persisted rows, `highestConfirmedUsedIndex`, and `handlerErrors` are
@@ -2339,8 +2355,12 @@ declare class ContractManager implements IContractManager {
2339
2355
  * can surface hits above the failed index that a serial scan would never
2340
2356
  * have reached, so a truncated batched scan discovers a superset — never
2341
2357
  * a subset — of the serial one.
2358
+ * - The whole scan runs inside one coalesced subscription scope, so N
2359
+ * discovered contracts cost ONE `subscribeForScripts` instead of N
2360
+ * growing ones (see {@link ContractWatcher.withCoalescedSubscription}).
2342
2361
  */
2343
2362
  scanContracts(opts: ScanContractsOptions): Promise<ScanResult>;
2363
+ private runScan;
2344
2364
  /**
2345
2365
  * Get contracts with optional filters.
2346
2366
  *
@@ -2770,6 +2790,27 @@ interface DiscoveryDeps {
2770
2790
  */
2771
2791
  interface Discoverable {
2772
2792
  discoverAt(index: number, descriptor: string, deps: DiscoveryDeps): Promise<DiscoveredContract[]>;
2793
+ /**
2794
+ * Optional: answer for a whole scan window in one batched round-trip.
2795
+ * The scanner prefers it over per-index `discoverAt` calls when present,
2796
+ * which is what keeps a 10-index window to 1-2 indexer requests instead
2797
+ * of one per index. Handlers whose source is inherently per-address (e.g.
2798
+ * boarding, on Esplora) implement only `discoverAt`.
2799
+ *
2800
+ * **All-or-nothing per call.** Either resolve with a map covering *every*
2801
+ * requested index (empty array = confirmed miss), or reject — a handler
2802
+ * whose inner chunk fails partway must discard the partial results and
2803
+ * reject, because a missing index would otherwise read as "no funds here"
2804
+ * and let restore close its gap window on a failed request. The scanner
2805
+ * enforces this rather than trusting it: an incomplete map is treated as a
2806
+ * rejection, making the whole requested range indeterminate (hits present
2807
+ * in it are still persisted) and truncating the scan at the range's first
2808
+ * index. Indices that were not requested are ignored.
2809
+ */
2810
+ discoverRange?(entries: readonly {
2811
+ index: number;
2812
+ descriptor: string;
2813
+ }[], deps: DiscoveryDeps): Promise<Map<number, DiscoveredContract[]>>;
2773
2814
  }
2774
2815
  /** Duck-typed guard (mirrors `hasReceiveRotatorFactory`). */
2775
2816
  declare function isDiscoverable(handler: ContractHandler<unknown> | undefined): handler is ContractHandler<unknown> & Discoverable;
@@ -2931,6 +2972,9 @@ declare class ContractWatcher {
2931
2972
  private reconnectAttempts;
2932
2973
  private reconnectTimeoutId?;
2933
2974
  private failsafePollIntervalId?;
2975
+ /** See {@link withCoalescedSubscription}. */
2976
+ private subscriptionBatchDepth;
2977
+ private subscriptionUpdateDeferred;
2934
2978
  /**
2935
2979
  * Create a contract watcher with the given providers and polling settings.
2936
2980
  *
@@ -3025,6 +3069,24 @@ declare class ContractWatcher {
3025
3069
  * Poll specific contracts and emit events for changes.
3026
3070
  */
3027
3071
  private pollContracts;
3072
+ /**
3073
+ * Run `fn` with subscription updates coalesced into a single
3074
+ * `subscribeForScripts` on the way out.
3075
+ *
3076
+ * {@link addContract} re-subscribes eagerly (the watcher may already be
3077
+ * running), and every subscribe posts the *whole* accumulated script list —
3078
+ * so a restore scan that discovers N contracts sends N growing POSTs,
3079
+ * quadratic in script-slots. Inside this scope those updates are only
3080
+ * marked dirty, flushed once on the way out (success and error path alike).
3081
+ *
3082
+ * A contract added inside the scope is therefore not streaming until the
3083
+ * flush. Nothing in the watcher closes that window — the failsafe poll
3084
+ * replays repository state and cannot see VTXOs no one has fetched yet. The
3085
+ * one caller, `scanContracts`, is covered because `Wallet.restore` follows
3086
+ * it with a bulk `refreshVtxos`. A new caller must provide its own
3087
+ * equivalent catch-up, or keep the scope short enough not to need one.
3088
+ */
3089
+ withCoalescedSubscription<T>(fn: () => Promise<T>): Promise<T>;
3028
3090
  private tryUpdateSubscription;
3029
3091
  /**
3030
3092
  * Update the subscription with scripts that should be watched.
@@ -1,4 +1,4 @@
1
- import { a as TaskQueue, c as TaskItem, d as TaskResult } from './taskRunner-Brnak0Rc.cjs';
1
+ import { c as TaskQueue, b as TaskItem, d as TaskResult } from './taskRunner-Ck0AouQU.cjs';
2
2
 
3
3
  /**
4
4
  * Minimal async key-value storage interface.
@@ -46,4 +46,4 @@ declare class AsyncStorageTaskQueue implements TaskQueue {
46
46
  private writeList;
47
47
  }
48
48
 
49
- export { AsyncStorageTaskQueue as A, type AsyncStorageLike as a };
49
+ export { type AsyncStorageLike as A, AsyncStorageTaskQueue as a };
@@ -1,4 +1,4 @@
1
- import { a as TaskQueue, c as TaskItem, d as TaskResult } from './taskRunner-B-2MHCYG.js';
1
+ import { c as TaskQueue, b as TaskItem, d as TaskResult } from './taskRunner-bs1AE_BL.js';
2
2
 
3
3
  /**
4
4
  * Minimal async key-value storage interface.
@@ -46,4 +46,4 @@ declare class AsyncStorageTaskQueue implements TaskQueue {
46
46
  private writeList;
47
47
  }
48
48
 
49
- export { AsyncStorageTaskQueue as A, type AsyncStorageLike as a };
49
+ export { type AsyncStorageLike as A, AsyncStorageTaskQueue as a };
@@ -1,5 +1,5 @@
1
- import { getRandomId, extendVirtualCoinForContract, warnAndFilterVtxosForScript, saveVtxosForContract } from './chunk-NYR5H6GR.js';
2
- import { getNormalizedVtxos } from './chunk-ZDDBTFVO.js';
1
+ import { getRandomId, extendVirtualCoinForContract, warnAndFilterVtxosForScript, saveVtxosForContract } from './chunk-T2IVKSZP.js';
2
+ import { getNormalizedVtxos } from './chunk-EFNLTS6Q.js';
3
3
 
4
4
  // src/worker/expo/taskRunner.ts
5
5
  async function runTasks(queue, processors, deps) {
@@ -92,5 +92,5 @@ var contractPollProcessor = {
92
92
  };
93
93
 
94
94
  export { CONTRACT_POLL_TASK_TYPE, contractPollProcessor, createTaskDependencies, runTasks };
95
- //# sourceMappingURL=chunk-G6J6AEMH.js.map
96
- //# sourceMappingURL=chunk-G6J6AEMH.js.map
95
+ //# sourceMappingURL=chunk-27DACHF6.js.map
96
+ //# sourceMappingURL=chunk-27DACHF6.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/worker/expo/taskRunner.ts","../src/worker/expo/processors/contractPollProcessor.ts"],"names":[],"mappings":"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","file":"chunk-G6J6AEMH.js","sourcesContent":["import type { TaskItem, TaskResult, TaskQueue } from \"./taskQueue\";\nimport type { WalletRepository } from \"../../repositories/walletRepository\";\nimport type { ContractRepository } from \"../../repositories/contractRepository\";\nimport type { IndexerProvider } from \"../../providers/indexer\";\nimport type { ArkProvider } from \"../../providers/ark\";\nimport type { ExtendedVirtualCoin, VirtualCoin } from \"../../wallet\";\nimport type { Contract } from \"../../contracts/types\";\nimport { getRandomId, extendVirtualCoinForContract } from \"../../wallet/utils\";\n\n/**\n * Shared dependencies injected into every processor at runtime.\n *\n * `extendVtxo` requires the owning contract — processors must resolve each\n * vtxo's `script` to a known contract (via the contract repository) before\n * calling this. The strict signature prevents the footgun where a missing\n * contract silently falls back to the wallet's default tapscript.\n */\nexport interface TaskDependencies {\n walletRepository: WalletRepository;\n contractRepository: ContractRepository;\n indexerProvider: IndexerProvider;\n arkProvider: ArkProvider;\n extendVtxo: (vtxo: VirtualCoin, contract: Contract) => ExtendedVirtualCoin;\n}\n\n/**\n * A stateless unit that handles one type of task item.\n *\n * Processors must not keep in-memory state across invocations —\n * all coordination lives in the @see TaskQueue and repositories.\n *\n * The `TDeps` parameter defaults to @see TaskDependencies but\n * can be overridden for domain-specific processors (e.g. swap processing).\n */\nexport interface TaskProcessor<TDeps = TaskDependencies> {\n readonly taskType: string;\n execute(item: TaskItem, deps: TDeps): Promise<Omit<TaskResult, \"id\" | \"executedAt\">>;\n}\n\n/**\n * Run all pending tasks from the queue through matching processors.\n *\n * For each task in the inbox:\n * 1. Find the processor whose `taskType` matches `task.type`.\n * 2. Execute it, producing a @see TaskResult.\n * 3. Push the result to the outbox and remove the task from the inbox.\n *\n * Tasks with no matching processor produce a `\"noop\"` result.\n * Processor errors produce a `\"failed\"` result with the error message.\n */\nexport async function runTasks<TDeps = TaskDependencies>(\n queue: TaskQueue,\n processors: TaskProcessor<TDeps>[],\n deps: TDeps,\n): Promise<TaskResult[]> {\n const tasks = await queue.getTasks();\n const processorMap = new Map(processors.map((p) => [p.taskType, p]));\n const results: TaskResult[] = [];\n\n for (const task of tasks) {\n const processor = processorMap.get(task.type);\n\n let partial: Omit<TaskResult, \"id\" | \"executedAt\">;\n\n if (!processor) {\n partial = {\n taskItemId: task.id,\n type: task.type,\n status: \"noop\",\n };\n } else {\n try {\n partial = await processor.execute(task, deps);\n } catch (error) {\n partial = {\n taskItemId: task.id,\n type: task.type,\n status: \"failed\",\n data: {\n error: error instanceof Error ? error.message : String(error),\n },\n };\n }\n }\n\n const result: TaskResult = {\n ...partial,\n id: getRandomId(),\n executedAt: Date.now(),\n };\n\n await queue.pushResult(result);\n await queue.removeTask(task.id);\n results.push(result);\n }\n\n return results;\n}\n\n/**\n * Options for @see createTaskDependencies.\n */\nexport interface CreateTaskDependenciesOptions {\n walletRepository: WalletRepository;\n contractRepository: ContractRepository;\n indexerProvider: IndexerProvider;\n arkProvider: ArkProvider;\n}\n\n/**\n * Build the @see TaskDependencies needed by task processors\n * (e.g. `src/worker/expo/processors/contractPollProcessor.ts`)\n *\n * This is the same construction that `defineExpoBackgroundTask` does\n * internally, extracted so that consumers with custom schedulers\n * (e.g. bare React Native with `react-native-background-fetch`)\n * can build deps without depending on Expo.\n */\nexport function createTaskDependencies(options: CreateTaskDependenciesOptions): TaskDependencies {\n return {\n ...options,\n extendVtxo: (vtxo: VirtualCoin, contract: Contract) =>\n extendVirtualCoinForContract(vtxo, contract),\n };\n}\n","import type { TaskItem, TaskResult } from \"../taskQueue\";\nimport type { TaskProcessor, TaskDependencies } from \"../taskRunner\";\nimport type { ExtendedVirtualCoin } from \"../../../wallet\";\nimport { getNormalizedVtxos } from \"../../../wallet/vtxo\";\nimport {\n warnAndFilterVtxosForScript,\n saveVtxosForContract,\n} from \"../../../contracts/vtxoOwnership\";\n\nexport const CONTRACT_POLL_TASK_TYPE = \"contract-poll\";\n\n/**\n * Polls the indexer for the latest VTXO state of every contract and\n * persists the results to the wallet repository.\n *\n * Replicates the polling subset of @see ContractManager.initialize:\n * 1. Load all contracts from the contract repository.\n * 2. Paginated fetch of every VTXO (including spent) from the indexer.\n * 3. Extend each VTXO with tapscript data.\n * 4. Save to the wallet repository.\n *\n * NOTE: the indexer query deliberately omits `spendableOnly`. Every\n * repository implements `saveVtxos` as an upsert with no batch delete,\n * so filtering to spendable-only would leave VTXOs that became spent\n * between polls marked as spendable forever. Fetching the full set lets\n * the upsert overwrite stale records with their latest state.\n */\nexport const contractPollProcessor: TaskProcessor = {\n taskType: CONTRACT_POLL_TASK_TYPE,\n\n async execute(\n item: TaskItem,\n deps: TaskDependencies,\n ): Promise<Omit<TaskResult, \"id\" | \"executedAt\">> {\n const { contractRepository, walletRepository, indexerProvider, extendVtxo } = deps;\n\n const contracts = await contractRepository.getContracts();\n let contractsProcessed = 0;\n let vtxosSaved = 0;\n\n for (const contract of contracts) {\n // Paginated fetch of spendable virtual outputs\n const pageSize = 100;\n let pageIndex = 0;\n let hasMore = true;\n const allVtxos: ExtendedVirtualCoin[] = [];\n\n while (hasMore) {\n const { vtxos, page } = await getNormalizedVtxos(indexerProvider, {\n scripts: [contract.script],\n pageIndex,\n pageSize,\n });\n\n for (const vtxo of vtxos) {\n allVtxos.push(extendVtxo(vtxo, contract));\n }\n\n hasMore = page ? vtxos.length === pageSize : false;\n pageIndex++;\n }\n\n // Skip wrong-script rows (legacy duplicates or indexer drift)\n // before persisting; the loop must keep going for the remaining\n // contracts even when one row is rejected.\n const filtered = warnAndFilterVtxosForScript(\n allVtxos,\n contract.script,\n \"contractPollProcessor\",\n );\n await saveVtxosForContract(walletRepository, contract, filtered);\n vtxosSaved += filtered.length;\n contractsProcessed++;\n }\n\n return {\n taskItemId: item.id,\n type: CONTRACT_POLL_TASK_TYPE,\n status: \"success\",\n data: { contractsProcessed, vtxosSaved },\n };\n },\n};\n"]}
1
+ {"version":3,"sources":["../src/worker/expo/taskRunner.ts","../src/worker/expo/processors/contractPollProcessor.ts"],"names":[],"mappings":"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","file":"chunk-27DACHF6.js","sourcesContent":["import type { TaskItem, TaskResult, TaskQueue } from \"./taskQueue\";\nimport type { WalletRepository } from \"../../repositories/walletRepository\";\nimport type { ContractRepository } from \"../../repositories/contractRepository\";\nimport type { IndexerProvider } from \"../../providers/indexer\";\nimport type { ArkProvider } from \"../../providers/ark\";\nimport type { ExtendedVirtualCoin, VirtualCoin } from \"../../wallet\";\nimport type { Contract } from \"../../contracts/types\";\nimport { getRandomId, extendVirtualCoinForContract } from \"../../wallet/utils\";\n\n/**\n * Shared dependencies injected into every processor at runtime.\n *\n * `extendVtxo` requires the owning contract — processors must resolve each\n * vtxo's `script` to a known contract (via the contract repository) before\n * calling this. The strict signature prevents the footgun where a missing\n * contract silently falls back to the wallet's default tapscript.\n */\nexport interface TaskDependencies {\n walletRepository: WalletRepository;\n contractRepository: ContractRepository;\n indexerProvider: IndexerProvider;\n arkProvider: ArkProvider;\n extendVtxo: (vtxo: VirtualCoin, contract: Contract) => ExtendedVirtualCoin;\n}\n\n/**\n * A stateless unit that handles one type of task item.\n *\n * Processors must not keep in-memory state across invocations —\n * all coordination lives in the @see TaskQueue and repositories.\n *\n * The `TDeps` parameter defaults to @see TaskDependencies but\n * can be overridden for domain-specific processors (e.g. swap processing).\n */\nexport interface TaskProcessor<TDeps = TaskDependencies> {\n readonly taskType: string;\n execute(item: TaskItem, deps: TDeps): Promise<Omit<TaskResult, \"id\" | \"executedAt\">>;\n}\n\n/**\n * Run all pending tasks from the queue through matching processors.\n *\n * For each task in the inbox:\n * 1. Find the processor whose `taskType` matches `task.type`.\n * 2. Execute it, producing a @see TaskResult.\n * 3. Push the result to the outbox and remove the task from the inbox.\n *\n * Tasks with no matching processor produce a `\"noop\"` result.\n * Processor errors produce a `\"failed\"` result with the error message.\n */\nexport async function runTasks<TDeps = TaskDependencies>(\n queue: TaskQueue,\n processors: TaskProcessor<TDeps>[],\n deps: TDeps,\n): Promise<TaskResult[]> {\n const tasks = await queue.getTasks();\n const processorMap = new Map(processors.map((p) => [p.taskType, p]));\n const results: TaskResult[] = [];\n\n for (const task of tasks) {\n const processor = processorMap.get(task.type);\n\n let partial: Omit<TaskResult, \"id\" | \"executedAt\">;\n\n if (!processor) {\n partial = {\n taskItemId: task.id,\n type: task.type,\n status: \"noop\",\n };\n } else {\n try {\n partial = await processor.execute(task, deps);\n } catch (error) {\n partial = {\n taskItemId: task.id,\n type: task.type,\n status: \"failed\",\n data: {\n error: error instanceof Error ? error.message : String(error),\n },\n };\n }\n }\n\n const result: TaskResult = {\n ...partial,\n id: getRandomId(),\n executedAt: Date.now(),\n };\n\n await queue.pushResult(result);\n await queue.removeTask(task.id);\n results.push(result);\n }\n\n return results;\n}\n\n/**\n * Options for @see createTaskDependencies.\n */\nexport interface CreateTaskDependenciesOptions {\n walletRepository: WalletRepository;\n contractRepository: ContractRepository;\n indexerProvider: IndexerProvider;\n arkProvider: ArkProvider;\n}\n\n/**\n * Build the @see TaskDependencies needed by task processors\n * (e.g. `src/worker/expo/processors/contractPollProcessor.ts`)\n *\n * This is the same construction that `defineExpoBackgroundTask` does\n * internally, extracted so that consumers with custom schedulers\n * (e.g. bare React Native with `react-native-background-fetch`)\n * can build deps without depending on Expo.\n */\nexport function createTaskDependencies(options: CreateTaskDependenciesOptions): TaskDependencies {\n return {\n ...options,\n extendVtxo: (vtxo: VirtualCoin, contract: Contract) =>\n extendVirtualCoinForContract(vtxo, contract),\n };\n}\n","import type { TaskItem, TaskResult } from \"../taskQueue\";\nimport type { TaskProcessor, TaskDependencies } from \"../taskRunner\";\nimport type { ExtendedVirtualCoin } from \"../../../wallet\";\nimport { getNormalizedVtxos } from \"../../../wallet/vtxo\";\nimport {\n warnAndFilterVtxosForScript,\n saveVtxosForContract,\n} from \"../../../contracts/vtxoOwnership\";\n\nexport const CONTRACT_POLL_TASK_TYPE = \"contract-poll\";\n\n/**\n * Polls the indexer for the latest VTXO state of every contract and\n * persists the results to the wallet repository.\n *\n * Replicates the polling subset of @see ContractManager.initialize:\n * 1. Load all contracts from the contract repository.\n * 2. Paginated fetch of every VTXO (including spent) from the indexer.\n * 3. Extend each VTXO with tapscript data.\n * 4. Save to the wallet repository.\n *\n * NOTE: the indexer query deliberately omits `spendableOnly`. Every\n * repository implements `saveVtxos` as an upsert with no batch delete,\n * so filtering to spendable-only would leave VTXOs that became spent\n * between polls marked as spendable forever. Fetching the full set lets\n * the upsert overwrite stale records with their latest state.\n */\nexport const contractPollProcessor: TaskProcessor = {\n taskType: CONTRACT_POLL_TASK_TYPE,\n\n async execute(\n item: TaskItem,\n deps: TaskDependencies,\n ): Promise<Omit<TaskResult, \"id\" | \"executedAt\">> {\n const { contractRepository, walletRepository, indexerProvider, extendVtxo } = deps;\n\n const contracts = await contractRepository.getContracts();\n let contractsProcessed = 0;\n let vtxosSaved = 0;\n\n for (const contract of contracts) {\n // Paginated fetch of spendable virtual outputs\n const pageSize = 100;\n let pageIndex = 0;\n let hasMore = true;\n const allVtxos: ExtendedVirtualCoin[] = [];\n\n while (hasMore) {\n const { vtxos, page } = await getNormalizedVtxos(indexerProvider, {\n scripts: [contract.script],\n pageIndex,\n pageSize,\n });\n\n for (const vtxo of vtxos) {\n allVtxos.push(extendVtxo(vtxo, contract));\n }\n\n hasMore = page ? vtxos.length === pageSize : false;\n pageIndex++;\n }\n\n // Skip wrong-script rows (legacy duplicates or indexer drift)\n // before persisting; the loop must keep going for the remaining\n // contracts even when one row is rejected.\n const filtered = warnAndFilterVtxosForScript(\n allVtxos,\n contract.script,\n \"contractPollProcessor\",\n );\n await saveVtxosForContract(walletRepository, contract, filtered);\n vtxosSaved += filtered.length;\n contractsProcessed++;\n }\n\n return {\n taskItemId: item.id,\n type: CONTRACT_POLL_TASK_TYPE,\n status: \"success\",\n data: { contractsProcessed, vtxosSaved },\n };\n },\n};\n"]}
@@ -1,5 +1,5 @@
1
- import { RestArkProvider, isFetchTimeoutError, RestIndexerProvider, buildVersion, sdkVersion, fetch } from './chunk-BHHLRZCS.js';
2
- import { DEFAULT_ARKADE_SERVER_URL, convertVtxo } from './chunk-ZDDBTFVO.js';
1
+ import { RestArkProvider, isFetchTimeoutError, RestIndexerProvider, buildVersion, sdkVersion, fetch } from './chunk-ITT2S4O3.js';
2
+ import { DEFAULT_ARKADE_SERVER_URL, convertVtxo } from './chunk-EFNLTS6Q.js';
3
3
 
4
4
  // src/providers/expoUtils.ts
5
5
  async function getExpoFetch(options) {
@@ -185,5 +185,5 @@ var ExpoIndexerProvider = class extends RestIndexerProvider {
185
185
  };
186
186
 
187
187
  export { ExpoArkProvider, ExpoIndexerProvider };
188
- //# sourceMappingURL=chunk-XFMGIFBL.js.map
189
- //# sourceMappingURL=chunk-XFMGIFBL.js.map
188
+ //# sourceMappingURL=chunk-COB6VP4M.js.map
189
+ //# sourceMappingURL=chunk-COB6VP4M.js.map