@arkade-os/sdk 0.5.0-rc.3 → 0.5.0-rc.31

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 (126) hide show
  1. package/README.md +223 -83
  2. package/dist/adapters/expo.cjs +5 -6
  3. package/dist/adapters/expo.d.cts +5 -5
  4. package/dist/adapters/expo.d.ts +5 -5
  5. package/dist/adapters/expo.js +3 -4
  6. package/dist/{asyncStorageTaskQueue-DAs_n1xr.d.ts → asyncStorageTaskQueue-DAVf4Fyy.d.ts} +2 -2
  7. package/dist/{asyncStorageTaskQueue-DqGRG66E.d.cts → asyncStorageTaskQueue-fkkUPrhz.d.cts} +2 -2
  8. package/dist/{chunk-BCT7DT2J.cjs → chunk-5XQNR5AK.cjs} +12 -10
  9. package/dist/chunk-5XQNR5AK.cjs.map +1 -0
  10. package/dist/{chunk-VQCDDGRW.js → chunk-6QKS7AJL.js} +6350 -4816
  11. package/dist/chunk-6QKS7AJL.js.map +1 -0
  12. package/dist/{chunk-UJTYUYY4.cjs → chunk-CUXM3LC5.cjs} +596 -467
  13. package/dist/chunk-CUXM3LC5.cjs.map +1 -0
  14. package/dist/{chunk-L4HZK4KK.cjs → chunk-GM6COBRW.cjs} +158 -263
  15. package/dist/chunk-GM6COBRW.cjs.map +1 -0
  16. package/dist/{chunk-UCSTNXB4.cjs → chunk-H5SOB5TL.cjs} +501 -1153
  17. package/dist/chunk-H5SOB5TL.cjs.map +1 -0
  18. package/dist/{chunk-G4UPMCKW.js → chunk-HRQAPER2.js} +33 -36
  19. package/dist/chunk-HRQAPER2.js.map +1 -0
  20. package/dist/{chunk-FSF2PGTR.cjs → chunk-MUKK2ETJ.cjs} +6736 -5189
  21. package/dist/chunk-MUKK2ETJ.cjs.map +1 -0
  22. package/dist/{chunk-ERUAQGYF.js → chunk-OQRIFUKZ.js} +146 -252
  23. package/dist/chunk-OQRIFUKZ.js.map +1 -0
  24. package/dist/{chunk-7W6FHWYN.js → chunk-PAAJWNX7.js} +434 -1109
  25. package/dist/chunk-PAAJWNX7.js.map +1 -0
  26. package/dist/{chunk-43XGA362.cjs → chunk-ZAAUE3MK.cjs} +40 -43
  27. package/dist/chunk-ZAAUE3MK.cjs.map +1 -0
  28. package/dist/{chunk-ROD5H7RX.js → chunk-ZIK5DLII.js} +7 -5
  29. package/dist/chunk-ZIK5DLII.js.map +1 -0
  30. package/dist/{chunk-HSG2GQTM.js → chunk-ZSP3UI5L.js} +586 -462
  31. package/dist/chunk-ZSP3UI5L.js.map +1 -0
  32. package/dist/contracts/handlers/index.cjs +9 -10
  33. package/dist/contracts/handlers/index.d.cts +5 -5
  34. package/dist/contracts/handlers/index.d.ts +5 -5
  35. package/dist/contracts/handlers/index.js +2 -3
  36. package/dist/{delegate-CZzIduW4.d.cts → delegate-BYoNu3lf.d.cts} +1 -1
  37. package/dist/{delegate-B9Bmrns7.d.ts → delegate-DsqzUVId.d.ts} +1 -1
  38. package/dist/index-CXNTURO_.d.cts +645 -0
  39. package/dist/{index-DnNIOqHS.d.ts → index-DhP68WBD.d.cts} +1698 -2402
  40. package/dist/{index-DnNIOqHS.d.cts → index-DhP68WBD.d.ts} +1698 -2402
  41. package/dist/index-pQr5y77P.d.ts +645 -0
  42. package/dist/index.cjs +407 -356
  43. package/dist/index.d.cts +1045 -1302
  44. package/dist/index.d.ts +1045 -1302
  45. package/dist/index.js +4 -5
  46. package/dist/repositories/realm/index.cjs +201 -106
  47. package/dist/repositories/realm/index.cjs.map +1 -1
  48. package/dist/repositories/realm/index.d.cts +28 -16
  49. package/dist/repositories/realm/index.d.ts +28 -16
  50. package/dist/repositories/realm/index.js +190 -95
  51. package/dist/repositories/realm/index.js.map +1 -1
  52. package/dist/repositories/sqlite/index.cjs +301 -189
  53. package/dist/repositories/sqlite/index.cjs.map +1 -1
  54. package/dist/repositories/sqlite/index.d.cts +32 -21
  55. package/dist/repositories/sqlite/index.d.ts +32 -21
  56. package/dist/repositories/sqlite/index.js +294 -182
  57. package/dist/repositories/sqlite/index.js.map +1 -1
  58. package/dist/{taskRunner-BDb5Ee2J.d.cts → taskRunner-C8H3bSsk.d.cts} +2 -2
  59. package/dist/{taskRunner-F7wNJcF3.d.ts → taskRunner-DKtBy8Yo.d.ts} +2 -2
  60. package/dist/wallet/expo/background.cjs +14 -15
  61. package/dist/wallet/expo/background.cjs.map +1 -1
  62. package/dist/wallet/expo/background.d.cts +10 -16
  63. package/dist/wallet/expo/background.d.ts +10 -16
  64. package/dist/wallet/expo/background.js +6 -7
  65. package/dist/wallet/expo/background.js.map +1 -1
  66. package/dist/wallet/expo/index.cjs +20 -23
  67. package/dist/wallet/expo/index.cjs.map +1 -1
  68. package/dist/wallet/expo/index.d.cts +8 -9
  69. package/dist/wallet/expo/index.d.ts +8 -9
  70. package/dist/wallet/expo/index.js +12 -15
  71. package/dist/wallet/expo/index.js.map +1 -1
  72. package/dist/wallet-BmAHDzK3.d.cts +1874 -0
  73. package/dist/wallet-C0W8EOnA.d.ts +1874 -0
  74. package/dist/worker/expo/index.cjs +9 -10
  75. package/dist/worker/expo/index.cjs.map +1 -1
  76. package/dist/worker/expo/index.d.cts +6 -6
  77. package/dist/worker/expo/index.d.ts +6 -6
  78. package/dist/worker/expo/index.js +5 -6
  79. package/dist/worker/expo/index.js.map +1 -1
  80. package/package.json +12 -51
  81. package/dist/adapters/asyncStorage.cjs +0 -48
  82. package/dist/adapters/asyncStorage.cjs.map +0 -1
  83. package/dist/adapters/asyncStorage.d.cts +0 -16
  84. package/dist/adapters/asyncStorage.d.ts +0 -16
  85. package/dist/adapters/asyncStorage.js +0 -46
  86. package/dist/adapters/asyncStorage.js.map +0 -1
  87. package/dist/adapters/fileSystem.cjs +0 -116
  88. package/dist/adapters/fileSystem.cjs.map +0 -1
  89. package/dist/adapters/fileSystem.d.cts +0 -17
  90. package/dist/adapters/fileSystem.d.ts +0 -17
  91. package/dist/adapters/fileSystem.js +0 -93
  92. package/dist/adapters/fileSystem.js.map +0 -1
  93. package/dist/adapters/indexedDB.cjs +0 -103
  94. package/dist/adapters/indexedDB.cjs.map +0 -1
  95. package/dist/adapters/indexedDB.d.cts +0 -18
  96. package/dist/adapters/indexedDB.d.ts +0 -18
  97. package/dist/adapters/indexedDB.js +0 -101
  98. package/dist/adapters/indexedDB.js.map +0 -1
  99. package/dist/adapters/localStorage.cjs +0 -50
  100. package/dist/adapters/localStorage.cjs.map +0 -1
  101. package/dist/adapters/localStorage.d.cts +0 -14
  102. package/dist/adapters/localStorage.d.ts +0 -14
  103. package/dist/adapters/localStorage.js +0 -48
  104. package/dist/adapters/localStorage.js.map +0 -1
  105. package/dist/chunk-43XGA362.cjs.map +0 -1
  106. package/dist/chunk-5BLDMQED.cjs +0 -18
  107. package/dist/chunk-5BLDMQED.cjs.map +0 -1
  108. package/dist/chunk-7W6FHWYN.js.map +0 -1
  109. package/dist/chunk-BCT7DT2J.cjs.map +0 -1
  110. package/dist/chunk-ERUAQGYF.js.map +0 -1
  111. package/dist/chunk-FSF2PGTR.cjs.map +0 -1
  112. package/dist/chunk-G4UPMCKW.js.map +0 -1
  113. package/dist/chunk-HSG2GQTM.js.map +0 -1
  114. package/dist/chunk-L4HZK4KK.cjs.map +0 -1
  115. package/dist/chunk-NSBPE2FW.js +0 -15
  116. package/dist/chunk-NSBPE2FW.js.map +0 -1
  117. package/dist/chunk-ROD5H7RX.js.map +0 -1
  118. package/dist/chunk-UCSTNXB4.cjs.map +0 -1
  119. package/dist/chunk-UJTYUYY4.cjs.map +0 -1
  120. package/dist/chunk-VQCDDGRW.js.map +0 -1
  121. package/dist/hdWalletCapable-BIl3yLVM.d.cts +0 -2232
  122. package/dist/hdWalletCapable-Cr5nGpK5.d.ts +0 -2232
  123. package/dist/index-C0IanN1m.d.cts +0 -11
  124. package/dist/index-C0IanN1m.d.ts +0 -11
  125. package/dist/index-Ci5MmNnK.d.cts +0 -850
  126. package/dist/index-DQ1Hb2w_.d.ts +0 -850
package/README.md CHANGED
@@ -306,6 +306,8 @@ const wallet = await OnchainWallet.create(identity, 'bitcoin', provider)
306
306
  await provider.close()
307
307
  ```
308
308
 
309
+ Where the network is only known at runtime (`Wallet.create`, or a service worker's `MessageBus`), pass a factory instead: `onchainProvider: (network) => new ElectrumOnchainProvider(new ElectrumWS(ELECTRUM_WS_URL[network]), networks[network])`. A `MessageBus` calls it on every init and never closes what it returns, so a worker should hand back one instance per network rather than a new connection each time.
310
+
309
311
  #### Atomic 1P1C package broadcast (TRUC / BIP 431)
310
312
 
311
313
  Both providers expose `broadcastTransaction(...txs)` that accepts either a single tx or a 1P1C package (parent first, child last). The package path is **atomic** — the parent doesn't have to independently meet mempool minfee, which is the point of TRUC relay.
@@ -591,7 +593,6 @@ Access the `VtxoManager` from the wallet after configuring `settlementConfig`:
591
593
  const manager = await wallet.getVtxoManager()
592
594
  ```
593
595
 
594
- > **Migration from `renewalConfig`:** Directly initializing a `VtxoManager` with `renewalConfig` is still supported but deprecated. Prefer `settlementConfig` where `vtxoThreshold` is expressed in **seconds** instead of milliseconds.
595
596
 
596
597
  #### Renewal: Prevent Expiration
597
598
 
@@ -799,6 +800,71 @@ const exitTxid = await new Ramps(wallet).offboard(
799
800
  service-worker and Expo wallets too. It answers with the server's live `ArkadeInfo`, falling back
800
801
  to the snapshot persisted at wallet construction when the server is unreachable.
801
802
 
803
+ ### Concurrent Spending & Coin Reservations
804
+
805
+ By default the wallet runs one spend at a time: a `send` issued while a settle or a collaborative
806
+ exit waits for its batch swap queues until that batch swap finalizes, and an exit with an amount
807
+ spends every spendable VTXO. Opt in to let spends overlap on disjoint VTXOs:
808
+
809
+ ```typescript
810
+ const wallet = await Wallet.create({ identity, arkProvider, concurrentSpending: true })
811
+ ```
812
+
813
+ With `concurrentSpending`:
814
+
815
+ - `send`, `settle` and asset operations hold the wallet lock only while they pick and reserve their
816
+ inputs; submitting the Arkade transaction and waiting for the batch swap run unlocked.
817
+ - `Ramps.offboard(address, fees, amount)` and `offboardExact` spend only the VTXOs that cover the
818
+ amount (soonest expiry first, as `send` picks), so the rest of the balance stays spendable. A
819
+ full exit, and an exit that names its `vtxos`, behave as before.
820
+ - Naming a VTXO that another operation is spending (`send({ selectedVtxos })`,
821
+ `settle({ inputs })`) throws `VtxoReservedError` before anything is submitted. Exits that pick
822
+ their own VTXOs retry the pick.
823
+ - Boarding UTXOs a settle is spending stay in `getBoardingUtxos()` and the boarding balance, but
824
+ a bare `settle()`, `Ramps.onboard` without explicit UTXOs and the boarding poll skip them until
825
+ that settle finishes, and naming one elsewhere throws `VtxoReservedError`.
826
+
827
+ Concurrency only helps when the balance sits in more than one VTXO: a wallet holding a single VTXO
828
+ still has one operation at a time. Renewal and a bare `settle()` merge the VTXOs they spend into
829
+ one output.
830
+
831
+ #### Holding VTXOs for your own use
832
+
833
+ `reserveVtxos` keeps VTXOs out of every pick the SDK makes for you — `send`, `settle()`, exits,
834
+ renewal, payment-router rails and swap funding — until you release them. You can still spend them
835
+ by naming them. It works with or without `concurrentSpending`.
836
+
837
+ ```typescript
838
+ const hold = wallet.reserveVtxos(coinsForThisOrder)
839
+ try {
840
+ await wallet.send({ recipients, selectedVtxos: coinsForThisOrder })
841
+ } finally {
842
+ hold.release()
843
+ }
844
+ ```
845
+
846
+ Held VTXOs stay in `getVtxos()` and in `getBalance().total`, and move from `available` to
847
+ `reserved`. Holding a VTXO that is held or being spent throws `VtxoReservedError`. If the SDK had
848
+ already picked a VTXO when you hold it, that spend throws `VtxoReservedError` (`holder: "held"`)
849
+ instead of spending it; exits and renewal pick again or retry on their own.
850
+
851
+ Limits:
852
+
853
+ - Reservations are in memory, per `Wallet` instance. Two processes or two `Wallet` instances on one
854
+ seed do not see each other's reservations; the operator refuses whichever spend loses.
855
+ - Deprecated-signer migration and recovery name the VTXOs they move, so they can spend a held VTXO.
856
+ - Renewal skips a held VTXO even once it is inside the renewal threshold, since renewing would
857
+ change the outpoint you are holding. It logs a warning naming each such VTXO; spend or release a
858
+ long-lived hold before the VTXO expires, or the server sweeps it and it comes back only through
859
+ recovery.
860
+ - `toReadonly()` views and the service worker's balance reads do not see reservations.
861
+ - `reserveVtxos` is on `Wallet`; the service-worker and Expo wallets accept `concurrentSpending`
862
+ but do not expose holds yet.
863
+
864
+ If your app keeps its own reserved-outpoint set, call `reserveVtxos` where you add to it and
865
+ `release()` where you remove, then let the SDK pick: plain `send`, router payments without
866
+ `selectedVtxos`, and exits without `vtxos` all skip what you hold.
867
+
802
868
  ### Talking to the server through the wallet
803
869
 
804
870
  `getArkadeInfo()` is one of three seams that let a plugin work from a wallet alone, never a
@@ -1092,6 +1158,37 @@ const balance = await wallet.getBalance()
1092
1158
  For watch-only wallets, use `ServiceWorkerReadonlyWallet` with a
1093
1159
  `ReadonlySingleKey` identity instead.
1094
1160
 
1161
+ ### Faster startup without changing existing reads
1162
+
1163
+ Existing `Wallet` and `ReadonlyWallet` reads (`getVtxos`, `getBalance`, and
1164
+ `getTransactionHistory`) still synchronize before answering. Retryable provider
1165
+ failures retain the existing offline fallback; terminal errors still reject.
1166
+ Contract initialization still awaits synchronization and watcher startup by default.
1167
+
1168
+ For a returning wallet with persisted contracts, opt into `lazyInitialization: true`
1169
+ and use `getStoredVtxos()` or `getStoredBalance()` for the initial display. These
1170
+ methods return cached data and request background synchronization. They can return
1171
+ empty or stale data, so inspect `(await wallet.getContractManager()).getSyncState()`:
1172
+ an absent `lastSyncedAt` means no successful sync this session, `syncing` means work
1173
+ is in flight, and `degraded` means refresh failed. Await `manager.whenBooted()` and
1174
+ inspect that state again before treating initialization as successful. Refresh the
1175
+ display after completion; cached reads do not guarantee a change event.
1176
+
1177
+ `getStoredBalance().boarding.loaded` is false until this wallet instance has fetched
1178
+ boarding inputs. Call `getBoardingUtxos()` to refresh them. Stored methods are for
1179
+ display, not transaction inputs; use the normal reads and spending APIs for actions.
1180
+ New contract registration, identity setup, and server-info validation can still
1181
+ require provider calls even with lazy initialization enabled.
1182
+
1183
+ Service-worker factories expose the same `lazyInitialization` option and a separate
1184
+ `lazyBoarding: true` option. Lazy boarding defers the explorer-backed boarding and
1185
+ history refresh, reports `boarding.loaded: false`, and broadcasts `UTXO_UPDATE`
1186
+ when boarding loading completes. Both options default to false and survive worker
1187
+ reinitialization. Existing `GET_BOARDING_UTXOS` requests remain live reads.
1188
+
1189
+ Boarding addresses are fetched concurrently in both modes, so wallets with rotated
1190
+ addresses benefit without opting into changed initialization timing.
1191
+
1095
1192
  ### Worker Architecture
1096
1193
 
1097
1194
  The _worker_ captures the background processing infrastructure for the SDK.
@@ -1109,78 +1206,50 @@ examples.
1109
1206
 
1110
1207
  ### Repositories (Storage)
1111
1208
 
1112
- The `StorageAdapter` API is deprecated. Use repositories instead. If you omit `storage`, the SDK uses IndexedDB repositories with the default database name.
1113
-
1114
- #### Migration from v1 StorageAdapter
1115
-
1116
- > [!WARNING]
1117
- > If you previously used the v1 `StorageAdapter`-based repositories, migrate
1118
- > data into the new IndexedDB repositories before use:
1119
- >
1120
- > ```typescript
1121
- > import {
1122
- > IndexedDBWalletRepository,
1123
- > IndexedDBContractRepository,
1124
- > getMigrationStatus,
1125
- > migrateWalletRepository,
1126
- > rollbackMigration,
1127
- > } from '@arkade-os/sdk'
1128
- > import { IndexedDBStorageAdapter } from '@arkade-os/sdk/adapters/indexedDB'
1129
- >
1130
- > const oldStorage = new IndexedDBStorageAdapter('legacy-wallet', 1)
1131
- > const newDbName = 'my-app-db'
1132
- > const walletRepository = new IndexedDBWalletRepository(newDbName)
1133
- >
1134
- > // Check migration status before running
1135
- > const status = await getMigrationStatus('wallet', oldStorage)
1136
- > // status: "not-needed" | "pending" | "in-progress" | "done"
1137
- >
1138
- > if (status === 'pending' || status === 'in-progress') {
1139
- > try {
1140
- > await migrateWalletRepository(oldStorage, walletRepository, {
1141
- > onchain: [ 'address-1', 'address-2' ],
1142
- > offchain: [ 'onboarding-address-1' ],
1143
- > })
1144
- > } catch (err) {
1145
- > // Reset migration flag so the next attempt starts clean
1146
- > await rollbackMigration('wallet', oldStorage)
1147
- > throw err
1148
- > }
1149
- > }
1150
- > ```
1151
- >
1152
- > **Migration status helpers:**
1153
- >
1154
- > | Helper | Description |
1155
- > |--------|-------------|
1156
- > | `getMigrationStatus(repoType, adapter)` | Returns `"not-needed"` (no legacy DB), `"pending"`, `"in-progress"` (interrupted), or `"done"` |
1157
- > | `requiresMigration(repoType, adapter)` | Returns `true` if status is `"pending"` or `"in-progress"` |
1158
- > | `rollbackMigration(repoType, adapter)` | Removes the migration flag so migration can re-run from scratch |
1159
- > | `MIGRATION_KEY(repoType)` | Returns the storage key used for the migration flag |
1160
- >
1161
- > `migrateWalletRepository` sets an `"in-progress"` flag before copying data.
1162
- > If the process crashes mid-way, the flag remains as `"in-progress"` so the
1163
- > next call to `getMigrationStatus` can detect the partial migration. Old data
1164
- > is never deleted — re-running migration after a rollback is safe.
1165
- >
1166
- > Anything related to contract repository migration must be handled by the
1167
- > package that created the contracts. The SDK doesn't manage external contracts
1168
- > in V1; data persisted by other packages remains untouched in its original
1169
- > location.
1209
+ Use repository implementations via `StorageConfig`. If you omit `storage`, the
1210
+ SDK uses IndexedDB repositories with the default database name.
1211
+
1212
+ #### Paged repository reads
1213
+
1214
+ Collection repositories expose `getVtxosPage`, `getVtxosForScriptPage`, `getUtxosPage`,
1215
+ `getTransactionHistoryPage`, `getContractsPage`, and `getIntentsPage`.
1216
+ Each accepts `{ limit, after? }` and returns
1217
+ `{ items, nextCursor? }`; pass `nextCursor` as `after` until it is absent.
1218
+ Limits must be 1–500. History pages are ordered by `(createdAt, transaction key)`;
1219
+ `since` is inclusive Unix milliseconds. Contract and intent pages are ordered
1220
+ by their script and intent transaction ID. Filters must stay the same across
1221
+ pages. Repository collection reads use pages directly; `collectVtxos`,
1222
+ `collectScriptVtxos`, `collectUtxos`, `collectTransactionHistory`, `collectContracts`, and
1223
+ `collectIntents` traverse all pages when a complete snapshot is required.
1224
+ Wallet spending paths consume every VTXO or UTXO page before coin selection.
1225
+ In `@arkade-os/swap`, RFQ page `since` values use Unix seconds instead.
1226
+
1227
+ ```ts
1228
+ let after;
1229
+ do {
1230
+ const page = await walletRepo.getTransactionHistoryPage(
1231
+ { address, since: Date.now() - 86_400_000 },
1232
+ { limit: 100, after },
1233
+ );
1234
+ for (const transaction of page.items) handle(transaction);
1235
+ after = page.nextCursor;
1236
+ } while (after);
1237
+ ```
1170
1238
 
1171
1239
  #### Repository Versioning
1172
1240
 
1173
- `WalletRepository`, `ContractRepository`, and `AssetSwapRepository` (in
1174
- `@arkade-os/swap`) each declare a `readonly version` field with a literal
1175
- type. All built-in implementations set this to the current version. If you
1176
- maintain a custom repository implementation, TypeScript will produce a compile
1177
- error when the version is bumped, signaling that a semantic update is required:
1241
+ `WalletRepository`, `ContractRepository`, `IntentRepository`,
1242
+ `VirtualTxRepository`, and `AssetSwapRepository` (in `@arkade-os/swap`) each
1243
+ declare a `readonly version` field with a literal type. All built-in
1244
+ implementations set this to the current version. If you maintain a custom
1245
+ repository implementation, TypeScript will produce a compile error when the
1246
+ version is bumped, signaling that a semantic update is required:
1178
1247
 
1179
1248
  ```typescript
1180
1249
  import { WalletRepository } from '@arkade-os/sdk'
1181
1250
 
1182
1251
  class MyWalletRepository implements WalletRepository {
1183
- readonly version = 1 // must match the interface's literal type
1252
+ readonly version = 2 // must match the interface's literal type
1184
1253
  // ...
1185
1254
  }
1186
1255
  ```
@@ -1190,17 +1259,21 @@ class MyWalletRepository implements WalletRepository {
1190
1259
  For Node.js or React Native environments, use the SQLite repository with any
1191
1260
  SQLite driver. The SDK accepts a `SQLExecutor` interface — you provide the
1192
1261
  driver, the SDK handles the schema.
1262
+ Paged wallet reads require SQLite 3.15.0 or newer because they use
1263
+ [row-value comparisons](https://sqlite.org/rowvalue.html#backwards_compatibility).
1264
+ The built-in `node:sqlite` module is experimental in Node.js 22.12.x and stable
1265
+ from Node.js 22.13.0 onward.
1193
1266
 
1194
1267
  See [examples/node/multiple-wallets.ts](examples/node/multiple-wallets.ts) for
1195
- a full working example using `better-sqlite3`.
1268
+ a full working example using `node:sqlite`.
1196
1269
 
1197
1270
  ```typescript
1198
1271
  import { MnemonicIdentity, Wallet } from '@arkade-os/sdk'
1199
1272
  import { SQLiteWalletRepository, SQLiteContractRepository, SQLExecutor } from '@arkade-os/sdk/repositories/sqlite'
1200
- import Database from 'better-sqlite3'
1273
+ import { DatabaseSync } from 'node:sqlite'
1201
1274
 
1202
- const db = new Database('my-wallet.sqlite')
1203
- db.pragma('journal_mode = WAL')
1275
+ const db = new DatabaseSync('my-wallet.sqlite')
1276
+ db.exec('PRAGMA journal_mode = WAL')
1204
1277
 
1205
1278
  const executor: SQLExecutor = {
1206
1279
  run: async (sql, params) => { db.prepare(sql).run(...(params ?? [])) },
@@ -1283,6 +1356,7 @@ const wallet = await Wallet.create({
1283
1356
  })
1284
1357
  ```
1285
1358
 
1359
+ ### Using with Node.js
1286
1360
  ### Using with Node.js
1287
1361
 
1288
1362
  Node.js does not provide a global `EventSource` implementation (24.x has one behind `--experimental-eventsource`). The SDK relies on `EventSource` for Server-Sent Events during settlement (onboarding/offboarding) and contract watching, so tell it which one to use:
@@ -1402,11 +1476,46 @@ When you call `wallet.notifyIncomingFunds()` or use `waitForIncomingFunds()`, it
1402
1476
 
1403
1477
  `watcherConfig.failsafePollIntervalMs` (default `20_000`) controls how often the watcher replays repository changes into contract events; it does not fetch fresh VTXOs from the indexer.
1404
1478
 
1479
+ #### Compiler artifacts
1480
+
1481
+ Read compiler format v1 JSON with `arkade.programFromArtifact`, which returns a normalized Program at version 1. `arkade.parseArtifact` reads normalized Program JSON and defaults omitted versions to 1. Only version 1 is supported, with typed `{ name, type }` declarations for every constructor parameter and call argument. Explicit version 0 programs, including persisted contracts, are rejected; there is no automatic migration. Older SDKs reject version 1 programs rather than silently reconstructing a different script. An Arkade function and its same-named Taproot leaf share one call, such as `contract.functions.claim(...)`. Standalone Taproot functions use their own names, such as `contract.functions.unilateral()`. Leaf order in the artifact still determines the Taproot tree. This example assumes a `digest: bytes32` constructor and a `claim` function with one `preimage: bytes32` covenant argument; providers, wallet, artifact JSON and matching digest/preimage values are already available.
1482
+
1483
+ Declarations retain the compiler's native scalar names: `pubkey`, `signature`, `bytes`, `bytes20`, `bytes32`, `asset`, `int`, `bool` and `AssetGroup`. There are no `hash` or `sig` type aliases. Byte types take `Uint8Array`; `int`, `bool` and `AssetGroup` take `bigint | number`, with `bool` restricted to `0` or `1`. Fixed widths follow the type itself, without extra width or boolean flags. The SDK requires every `pubkey` to be a 32-byte x-only key, although the language treats it as a bytes alias. Structs and arrays retain the compiler's flattened scalar field names and ordering.
1484
+
1485
+ The normalized Program remains an SDK representation: compiler `constructorInputs` become `params`, spend groups and leaves become named `functions`, and covenant `arkade` becomes `arkadeScript`. `parseArtifact` and `stringifyArtifact` read and write that normalized representation; `programFromArtifact` reads the compiler format. Existing deployment and call methods use the same syntax for imported and hand-written programs.
1486
+
1487
+ Before upgrading a wallet with persisted v0 contracts, back up its contract rows and retain a compatible SDK. After the upgrade, `manager.getUnsupportedContracts()` lists the v0 rows the wallet still holds: they stay stored, but their VTXOs are not synced and they cannot be spent or exited. To recover a contract in v1, reconstruct its program with typed declarations while preserving the stored constructor values, signer keys, leaf order and emulator position. Compile with the stored keys and compare the resulting `pkScript` with the original; reconnecting to a server with new keys or defaults can change the address. Only after the scripts match, register the reconstructed contract: over a v0 row with the same script, `register()` (like `manager.createContract`) replaces the row's params in place, then syncs its VTXOs. The row keeps its history, and its label and metadata unless you pass new ones. It leaves any other existing row unchanged. If the scripts differ, continue managing the contract with the compatible SDK. Changing the version number alone is not a migration.
1488
+
1489
+ ```typescript
1490
+ import { arkade, ArkAddress } from '@arkade-os/sdk'
1491
+
1492
+ const program = arkade.programFromArtifact(artifactJson)
1493
+ const client = await arkade.Arkade.connect({
1494
+ arkade: arkProvider,
1495
+ emulator: emulatorProvider,
1496
+ indexer: indexerProvider,
1497
+ identity: wallet.identity,
1498
+ contractManager: await wallet.getContractManager(),
1499
+ })
1500
+ const contract = client.contract(program, { digest })
1501
+ await contract.register({ label: 'Hashlock' })
1502
+ await wallet.send({ address: contract.address, amount: 10_000 })
1503
+
1504
+ // Call after funding appears in contract.getUtxos().
1505
+ const { txid } = await contract.functions.claim(preimage)
1506
+ .to(ArkAddress.decode(recipientAddress).pkScript, 10_000n)
1507
+ .send()
1508
+ ```
1509
+
1510
+ The connected server supplies `<SERVER_KEY>` (the reserved `server` parameter in normalized Programs) and `<SERVER_EXIT_DELAY>`; conflicting overrides fail, and time-based delays that are not multiples of 512 seconds fail rather than changing the advertised delay. Resolved bindings are persisted, so restoring the contract keeps its script through server key or policy changes. A declared constructor named `user` defaults to the connected identity; other constructor keys and flattened struct/array fields remain caller-provided, using names such as `owners.0.key`. Child-output placeholders remain explicit `bytes32` constructor bindings. Leaf call inputs used as script operands cannot produce a fixed address and are rejected; raw CSV operands must be canonical BIP68 encodings.
1511
+
1512
+ For compiler-derived programs, call arguments are covenant inputs in declaration order, followed by non-signature leaf inputs in declaration order. Same-named inputs in the two scopes remain separate positional arguments. Leaf signatures are PSBT signatures, not condition arguments: `send()` supports the connected identity and infrastructure signers; additional identities and covenant-tweaked leaf keys require explicit PSBT signing via `build()`. Covenant signature arguments remain caller-provided; transaction-bound Arkade `checkSig` signatures require an Arkade-specific digest helper, which is deferred. CSV leaves require aged onchain outputs and cannot use this offchain builder. Typed call validation and these spending limits apply to every normalized Program. The public `SignerRef` union is unchanged; version 1 programs store emulator signer placement in optional `emulatorIndex` metadata.
1513
+
1405
1514
  #### HD look-ahead window
1406
1515
 
1407
1516
  HD wallets (`walletMode: 'hd'`, or an explicit HD `DescriptorProvider`) also watch a band of *unused* offchain receive scripts around their allocation watermark, so a payment to an address that some other party issued from the same seed — a merchant backend such as BTCPay Server, or the .NET SDK driving the same wallet — arrives without the user calling `restore()`. `lookAheadWindow` (default `20`) is the per-side width of that band: the wallet watches `[watermark - N, watermark + N]`. Speculative entries are subscription-only; they become contract rows, and enter balances, only once funded.
1408
1517
 
1409
- The issuer, not the wallet, picks the contract shape, so each index is watched at *every* variant the wallet could own there: `default` and `delegate`, crossed with the unilateral-exit timelock matrix and the operator's current plus deprecated signers. That is the candidate set `restore()` probes too, so watching and restoring cover identical scripts.
1518
+ The issuer, not the wallet, picks the contract shape, so each index is watched at *every* variant the wallet could own there: `default` and `delegate`, crossed with the unilateral-exit timelock matrix and the operator's active plus retired signers. That is the candidate set `restore()` probes too, so watching and restoring cover identical scripts.
1410
1519
 
1411
1520
  ```typescript
1412
1521
  const wallet = await Wallet.create({
@@ -1426,6 +1535,35 @@ const swWallet = await ServiceWorkerWallet.setup({
1426
1535
 
1427
1536
  Raise it when the external issuer is expected to burn more than `N` consecutive addresses without any of them being paid — every index in such a run is a miss, and the funded one sits past the band. When that happens the funds are invisible until a `restore()` whose `gapLimit` is large enough to cross the run (`wallet.restore({ gapLimit: 200 })`); a default restore closes its gap window before reaching the funded index. Keep the value modest: the band adds up to `2N + 1` indices × the candidate matrix (typically 1-4 scripts each) to the wallet's subscription.
1428
1537
 
1538
+ #### Restoring plugin state
1539
+
1540
+ `wallet.restore()` is the explicit imported-wallet recovery boundary. It first restores the
1541
+ wallet's own addresses, contracts, history, and balances, then runs every restore hook registered
1542
+ for that wallet instance. `Wallet` and `ServiceWorkerWallet` both follow this ordering, and
1543
+ concurrent calls coalesce until the hooks finish.
1544
+
1545
+ ```typescript
1546
+ import { registerWalletRestoreHook } from '@arkade-os/sdk'
1547
+
1548
+ const unregister = registerWalletRestoreHook(wallet, {
1549
+ id: 'my-plugin',
1550
+ restore: async (restoredWallet) => {
1551
+ await rebuildPluginState(await restoredWallet.getTransactionHistory())
1552
+ },
1553
+ })
1554
+
1555
+ await wallet.restore()
1556
+ ```
1557
+
1558
+ Registering the same `id` again replaces that hook without changing its position. A run uses a
1559
+ stable snapshot, attempts every hook in registration order, and throws an `AggregateError` after
1560
+ all failures have been collected. If core recovery fails, hooks do not run. The returned function
1561
+ removes only that exact registration and is safe to call more than once.
1562
+
1563
+ Hooks do not run during `Wallet.create()`, `ServiceWorkerWallet.setup()`, or ordinary startup. A
1564
+ plugin should register before an application calls `restore()` and keep its normal incremental
1565
+ reconciliation for state that arrives later.
1566
+
1429
1567
  For advanced use cases, you can access the ContractManager directly to register external contracts:
1430
1568
 
1431
1569
  ```typescript
@@ -1471,7 +1609,7 @@ const paths = manager.getSpendablePaths({
1471
1609
  contractScript: contract.script,
1472
1610
  vtxo,
1473
1611
  collaborative: true,
1474
- walletPubKey: myPubKey,
1612
+ walletDescriptor: myDescriptor,
1475
1613
  })
1476
1614
  if (paths.length > 0) {
1477
1615
  console.log('Contract is spendable via:', paths[0].leaf)
@@ -1481,7 +1619,7 @@ if (paths.length > 0) {
1481
1619
  const allPaths = await manager.getAllSpendingPaths({
1482
1620
  contractScript: contract.script,
1483
1621
  collaborative: true,
1484
- walletPubKey: myPubKey,
1622
+ walletDescriptor: myDescriptor,
1485
1623
  })
1486
1624
 
1487
1625
  // Fetch contracts together with their current virtual outputs
@@ -1523,14 +1661,16 @@ versions keep the coverage they have today.
1523
1661
  Most users don't need to touch repositories directly — `Wallet` reads through them and `ContractManager` owns VTXO/contract synchronization into them. They are documented here for advanced integrations (custom storage backends, offline-first apps, repository inspection).
1524
1662
 
1525
1663
  ```typescript
1664
+ import { collectContracts, collectTransactionHistory, collectUtxos, collectVtxos } from '@arkade-os/sdk'
1665
+
1526
1666
  // Wallet repository — VTXOs, UTXOs, transaction history, settings
1527
1667
  const addr = await wallet.getAddress()
1528
- const vtxos = await wallet.walletRepository.getVtxos(addr)
1529
- const utxos = await wallet.walletRepository.getUtxos(addr)
1530
- const history = await wallet.walletRepository.getTransactionHistory(addr)
1668
+ const vtxos = await collectVtxos(wallet.walletRepository, addr)
1669
+ const utxos = await collectUtxos(wallet.walletRepository, addr)
1670
+ const history = await collectTransactionHistory(wallet.walletRepository, addr)
1531
1671
 
1532
1672
  // Contract repository — script-keyed contracts (default address, VHTLCs, etc.)
1533
- const contracts = await wallet.contractRepository.getContracts({ type: 'vhtlc' })
1673
+ const contracts = await collectContracts(wallet.contractRepository, { type: 'vhtlc' })
1534
1674
  await wallet.contractRepository.saveContract(myContract)
1535
1675
  await wallet.contractRepository.deleteContract(myContract.script)
1536
1676
  ```
@@ -1544,15 +1684,15 @@ This package is developed inside the [arkade-os/ts-sdk](../..) monorepo. See the
1544
1684
  Common package-local commands (run from the repo root):
1545
1685
 
1546
1686
  ```bash
1547
- pnpm -C packages/ts-sdk run typecheck # Type-check the SDK
1548
- pnpm -C packages/ts-sdk test:unit # Unit tests, excluding e2e
1549
- pnpm -C packages/ts-sdk test:watch # Vitest watch mode
1550
- pnpm -C packages/ts-sdk test:coverage # Coverage report
1551
- pnpm -C packages/ts-sdk docs:build # Build TypeDoc API docs
1552
- pnpm -C packages/ts-sdk docs:open # Open API docs in the browser
1687
+ pnpm -C packages/sdk run typecheck # Type-check the SDK
1688
+ pnpm -C packages/sdk test:unit # Unit tests, excluding e2e
1689
+ pnpm -C packages/sdk test:watch # Vitest watch mode
1690
+ pnpm -C packages/sdk test:coverage # Coverage report
1691
+ pnpm -C packages/sdk docs:build # Build TypeDoc API docs
1692
+ pnpm -C packages/sdk docs:open # Open API docs in the browser
1553
1693
  ```
1554
1694
 
1555
- For integration tests, use the root commands (`pnpm run test:integration:ts-sdk` and the `regtest:*:ts-sdk` family) — see the root README.
1695
+ For integration tests, use the root commands (`pnpm run test:integration:sdk` and the `regtest:*:sdk` family) — see the root README.
1556
1696
 
1557
1697
  ### Releasing
1558
1698
 
@@ -1,19 +1,18 @@
1
1
  'use strict';
2
2
 
3
- var chunk43XGA362_cjs = require('../chunk-43XGA362.cjs');
4
- require('../chunk-L4HZK4KK.cjs');
5
- require('../chunk-UJTYUYY4.cjs');
6
- require('../chunk-5BLDMQED.cjs');
3
+ var chunkZAAUE3MK_cjs = require('../chunk-ZAAUE3MK.cjs');
4
+ require('../chunk-GM6COBRW.cjs');
5
+ require('../chunk-CUXM3LC5.cjs');
7
6
 
8
7
 
9
8
 
10
9
  Object.defineProperty(exports, "ExpoArkProvider", {
11
10
  enumerable: true,
12
- get: function () { return chunk43XGA362_cjs.ExpoArkProvider; }
11
+ get: function () { return chunkZAAUE3MK_cjs.ExpoArkProvider; }
13
12
  });
14
13
  Object.defineProperty(exports, "ExpoIndexerProvider", {
15
14
  enumerable: true,
16
- get: function () { return chunk43XGA362_cjs.ExpoIndexerProvider; }
15
+ get: function () { return chunkZAAUE3MK_cjs.ExpoIndexerProvider; }
17
16
  });
18
17
  //# sourceMappingURL=expo.cjs.map
19
18
  //# sourceMappingURL=expo.cjs.map
@@ -1,9 +1,9 @@
1
- import { r as RestArkProvider, o as SettlementEvent, T as TxNotificationEvent, s as RestIndexerProvider, t as SubscriptionResponse } from '../index-DnNIOqHS.cjs';
2
- export { u as ArkProvider, v as IndexerProvider } from '../index-DnNIOqHS.cjs';
1
+ import { r as RestArkProvider, o as SettlementEvent, T as TxNotificationEvent, s as RestIndexerProvider, t as SubscriptionEvent, u as SubscriptionHeartbeat } from '../index-DhP68WBD.cjs';
2
+ export { v as ArkProvider, w as IndexerProvider } from '../index-DhP68WBD.cjs';
3
3
  import '@scure/btc-signer/utils.js';
4
- import '@scure/btc-signer/transaction.js';
5
- import '@scure/btc-signer';
6
4
  import '@scure/btc-signer/psbt.js';
5
+ import '@scure/btc-signer';
6
+ import '@scure/btc-signer/transaction.js';
7
7
 
8
8
  /**
9
9
  * Expo-compatible Arkade provider implementation using expo/fetch for SSE support.
@@ -39,7 +39,7 @@ declare class ExpoArkProvider extends RestArkProvider {
39
39
  */
40
40
  declare class ExpoIndexerProvider extends RestIndexerProvider {
41
41
  constructor(serverUrl?: string);
42
- getSubscription(subscriptionId: string, abortSignal: AbortSignal): AsyncIterableIterator<SubscriptionResponse>;
42
+ protected subscriptionStream(url: string, abortSignal: AbortSignal): AsyncIterableIterator<SubscriptionEvent | SubscriptionHeartbeat>;
43
43
  }
44
44
 
45
45
  export { ExpoArkProvider, ExpoIndexerProvider };
@@ -1,9 +1,9 @@
1
- import { r as RestArkProvider, o as SettlementEvent, T as TxNotificationEvent, s as RestIndexerProvider, t as SubscriptionResponse } from '../index-DnNIOqHS.js';
2
- export { u as ArkProvider, v as IndexerProvider } from '../index-DnNIOqHS.js';
1
+ import { r as RestArkProvider, o as SettlementEvent, T as TxNotificationEvent, s as RestIndexerProvider, t as SubscriptionEvent, u as SubscriptionHeartbeat } from '../index-DhP68WBD.js';
2
+ export { v as ArkProvider, w as IndexerProvider } from '../index-DhP68WBD.js';
3
3
  import '@scure/btc-signer/utils.js';
4
- import '@scure/btc-signer/transaction.js';
5
- import '@scure/btc-signer';
6
4
  import '@scure/btc-signer/psbt.js';
5
+ import '@scure/btc-signer';
6
+ import '@scure/btc-signer/transaction.js';
7
7
 
8
8
  /**
9
9
  * Expo-compatible Arkade provider implementation using expo/fetch for SSE support.
@@ -39,7 +39,7 @@ declare class ExpoArkProvider extends RestArkProvider {
39
39
  */
40
40
  declare class ExpoIndexerProvider extends RestIndexerProvider {
41
41
  constructor(serverUrl?: string);
42
- getSubscription(subscriptionId: string, abortSignal: AbortSignal): AsyncIterableIterator<SubscriptionResponse>;
42
+ protected subscriptionStream(url: string, abortSignal: AbortSignal): AsyncIterableIterator<SubscriptionEvent | SubscriptionHeartbeat>;
43
43
  }
44
44
 
45
45
  export { ExpoArkProvider, ExpoIndexerProvider };
@@ -1,6 +1,5 @@
1
- export { ExpoArkProvider, ExpoIndexerProvider } from '../chunk-G4UPMCKW.js';
2
- import '../chunk-ERUAQGYF.js';
3
- import '../chunk-HSG2GQTM.js';
4
- import '../chunk-NSBPE2FW.js';
1
+ export { ExpoArkProvider, ExpoIndexerProvider } from '../chunk-HRQAPER2.js';
2
+ import '../chunk-OQRIFUKZ.js';
3
+ import '../chunk-ZSP3UI5L.js';
5
4
  //# sourceMappingURL=expo.js.map
6
5
  //# sourceMappingURL=expo.js.map
@@ -1,4 +1,4 @@
1
- import { c as TaskQueue, a as TaskItem, b as TaskResult } from './taskRunner-F7wNJcF3.js';
1
+ import { a as TaskQueue, c as TaskItem, d as TaskResult } from './taskRunner-DKtBy8Yo.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 { type AsyncStorageLike as A, AsyncStorageTaskQueue as a };
49
+ export { AsyncStorageTaskQueue as A, type AsyncStorageLike as a };
@@ -1,4 +1,4 @@
1
- import { c as TaskQueue, a as TaskItem, b as TaskResult } from './taskRunner-BDb5Ee2J.cjs';
1
+ import { a as TaskQueue, c as TaskItem, d as TaskResult } from './taskRunner-C8H3bSsk.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 { type AsyncStorageLike as A, AsyncStorageTaskQueue as a };
49
+ export { AsyncStorageTaskQueue as A, type AsyncStorageLike as a };
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkFSF2PGTR_cjs = require('./chunk-FSF2PGTR.cjs');
4
- var chunkUJTYUYY4_cjs = require('./chunk-UJTYUYY4.cjs');
3
+ var chunkMUKK2ETJ_cjs = require('./chunk-MUKK2ETJ.cjs');
4
+ var chunkCUXM3LC5_cjs = require('./chunk-CUXM3LC5.cjs');
5
5
 
6
6
  // src/worker/expo/taskRunner.ts
7
7
  async function runTasks(queue, processors, deps) {
@@ -33,7 +33,7 @@ async function runTasks(queue, processors, deps) {
33
33
  }
34
34
  const result = {
35
35
  ...partial,
36
- id: chunkFSF2PGTR_cjs.getRandomId(),
36
+ id: chunkMUKK2ETJ_cjs.getRandomId(),
37
37
  executedAt: Date.now()
38
38
  };
39
39
  await queue.pushResult(result);
@@ -45,7 +45,7 @@ async function runTasks(queue, processors, deps) {
45
45
  function createTaskDependencies(options) {
46
46
  return {
47
47
  ...options,
48
- extendVtxo: (vtxo, contract) => chunkFSF2PGTR_cjs.extendVirtualCoinForContract(vtxo, contract)
48
+ extendVtxo: (vtxo, contract) => chunkMUKK2ETJ_cjs.extendVirtualCoinForContract(vtxo, contract)
49
49
  };
50
50
  }
51
51
 
@@ -55,7 +55,9 @@ var contractPollProcessor = {
55
55
  taskType: CONTRACT_POLL_TASK_TYPE,
56
56
  async execute(item, deps) {
57
57
  const { contractRepository, walletRepository, indexerProvider, extendVtxo } = deps;
58
- const contracts = (await contractRepository.getContracts()).filter(chunkFSF2PGTR_cjs.isWatchedContract);
58
+ const contracts = await chunkMUKK2ETJ_cjs.collectContracts(contractRepository, {
59
+ watch: ["watched", "awaiting-funds"]
60
+ });
59
61
  let contractsProcessed = 0;
60
62
  let vtxosSaved = 0;
61
63
  for (const contract of contracts) {
@@ -64,7 +66,7 @@ var contractPollProcessor = {
64
66
  let hasMore = true;
65
67
  const allVtxos = [];
66
68
  while (hasMore) {
67
- const { vtxos, page } = await chunkUJTYUYY4_cjs.getNormalizedVtxos(indexerProvider, {
69
+ const { vtxos, page } = await chunkCUXM3LC5_cjs.getNormalizedVtxos(indexerProvider, {
68
70
  scripts: [contract.script],
69
71
  pageIndex,
70
72
  pageSize
@@ -75,12 +77,12 @@ var contractPollProcessor = {
75
77
  hasMore = page ? vtxos.length === pageSize : false;
76
78
  pageIndex++;
77
79
  }
78
- const filtered = chunkFSF2PGTR_cjs.warnAndFilterVtxosForScript(
80
+ const filtered = chunkMUKK2ETJ_cjs.warnAndFilterVtxosForScript(
79
81
  allVtxos,
80
82
  contract.script,
81
83
  "contractPollProcessor"
82
84
  );
83
- await chunkFSF2PGTR_cjs.saveVtxosForContract(walletRepository, contract, filtered);
85
+ await chunkMUKK2ETJ_cjs.saveVtxosForContract(walletRepository, contract, filtered);
84
86
  vtxosSaved += filtered.length;
85
87
  contractsProcessed++;
86
88
  }
@@ -97,5 +99,5 @@ exports.CONTRACT_POLL_TASK_TYPE = CONTRACT_POLL_TASK_TYPE;
97
99
  exports.contractPollProcessor = contractPollProcessor;
98
100
  exports.createTaskDependencies = createTaskDependencies;
99
101
  exports.runTasks = runTasks;
100
- //# sourceMappingURL=chunk-BCT7DT2J.cjs.map
101
- //# sourceMappingURL=chunk-BCT7DT2J.cjs.map
102
+ //# sourceMappingURL=chunk-5XQNR5AK.cjs.map
103
+ //# sourceMappingURL=chunk-5XQNR5AK.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/worker/expo/taskRunner.ts","../src/worker/expo/processors/contractPollProcessor.ts"],"names":["getRandomId","extendVirtualCoinForContract","collectContracts","getNormalizedVtxos","warnAndFilterVtxosForScript","saveVtxosForContract"],"mappings":"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","file":"chunk-5XQNR5AK.cjs","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 { collectContracts } from \"../../../repositories/contractRepository\";\nimport 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 every watched contract 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 // Background channel, so it covers exactly what the watcher's\n // subscription covers: `retained` rows are kept for reads only.\n const contracts = await collectContracts(contractRepository, {\n watch: [\"watched\", \"awaiting-funds\"],\n });\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"]}