@arkade-os/sdk 0.5.0-rc.9 → 0.5.1

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 (94) hide show
  1. package/README.md +232 -51
  2. package/dist/adapters/expo.cjs +5 -5
  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 -3
  6. package/dist/{asyncStorageTaskQueue-DZYrAWN8.d.ts → asyncStorageTaskQueue-CdWheMqT.d.ts} +1 -1
  7. package/dist/{asyncStorageTaskQueue-ChKX-D51.d.cts → asyncStorageTaskQueue-DFt5LgW5.d.cts} +1 -1
  8. package/dist/{chunk-R4KRWX7R.js → chunk-3M22PJVB.js} +427 -1078
  9. package/dist/chunk-3M22PJVB.js.map +1 -0
  10. package/dist/{chunk-J2K7WWMJ.js → chunk-6MIGYYWB.js} +112 -243
  11. package/dist/chunk-6MIGYYWB.js.map +1 -0
  12. package/dist/{chunk-FQCMEAWN.js → chunk-FXVEOEYB.js} +32 -39
  13. package/dist/chunk-FXVEOEYB.js.map +1 -0
  14. package/dist/{chunk-7OL2P5TV.cjs → chunk-GQUFPTN7.cjs} +39 -46
  15. package/dist/chunk-GQUFPTN7.cjs.map +1 -0
  16. package/dist/{chunk-VMVHRKXM.cjs → chunk-GW42FS2X.cjs} +494 -1122
  17. package/dist/chunk-GW42FS2X.cjs.map +1 -0
  18. package/dist/{chunk-V4JZOFYT.cjs → chunk-HQNQ3CLQ.cjs} +124 -254
  19. package/dist/chunk-HQNQ3CLQ.cjs.map +1 -0
  20. package/dist/{chunk-PO2ZDZZC.cjs → chunk-JJ6QKTFZ.cjs} +12 -10
  21. package/dist/chunk-JJ6QKTFZ.cjs.map +1 -0
  22. package/dist/{chunk-3TCWQPAC.js → chunk-NKKJEA3Z.js} +5842 -4284
  23. package/dist/chunk-NKKJEA3Z.js.map +1 -0
  24. package/dist/{chunk-JFZBKPSV.js → chunk-QFB55EOZ.js} +541 -400
  25. package/dist/chunk-QFB55EOZ.js.map +1 -0
  26. package/dist/{chunk-PKD5VPYX.cjs → chunk-QKNSMVBW.cjs} +6143 -4565
  27. package/dist/chunk-QKNSMVBW.cjs.map +1 -0
  28. package/dist/{chunk-VTXMC4N7.cjs → chunk-XLHUGP3G.cjs} +548 -399
  29. package/dist/chunk-XLHUGP3G.cjs.map +1 -0
  30. package/dist/{chunk-Z3QM5XFY.js → chunk-XQRIY5K5.js} +7 -5
  31. package/dist/chunk-XQRIY5K5.js.map +1 -0
  32. package/dist/contracts/handlers/index.cjs +9 -9
  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 -2
  36. package/dist/{delegate-CyBnNY9v.d.ts → delegate-BSbGQabI.d.ts} +1 -1
  37. package/dist/{delegate-Cyv2suFd.d.cts → delegate-CFCfrReG.d.cts} +1 -1
  38. package/dist/{index-BIL9boGL.d.ts → index-B3fRWO5i.d.cts} +1652 -2291
  39. package/dist/{index-BIL9boGL.d.cts → index-B3fRWO5i.d.ts} +1652 -2291
  40. package/dist/index-CzUVvIxA.d.ts +645 -0
  41. package/dist/index-Fpf4mQZr.d.cts +645 -0
  42. package/dist/index.cjs +399 -311
  43. package/dist/index.d.cts +957 -1124
  44. package/dist/index.d.ts +957 -1124
  45. package/dist/index.js +4 -4
  46. package/dist/repositories/realm/index.cjs +165 -103
  47. package/dist/repositories/realm/index.cjs.map +1 -1
  48. package/dist/repositories/realm/index.d.cts +18 -14
  49. package/dist/repositories/realm/index.d.ts +18 -14
  50. package/dist/repositories/realm/index.js +153 -91
  51. package/dist/repositories/realm/index.js.map +1 -1
  52. package/dist/repositories/sqlite/index.cjs +226 -184
  53. package/dist/repositories/sqlite/index.cjs.map +1 -1
  54. package/dist/repositories/sqlite/index.d.cts +22 -21
  55. package/dist/repositories/sqlite/index.d.ts +22 -21
  56. package/dist/repositories/sqlite/index.js +218 -176
  57. package/dist/repositories/sqlite/index.js.map +1 -1
  58. package/dist/{taskRunner-Cu4uO1to.d.ts → taskRunner-CdfoCMaw.d.ts} +1 -1
  59. package/dist/{taskRunner-DxpSuKmh.d.cts → taskRunner-Dk6oDAtd.d.cts} +1 -1
  60. package/dist/wallet/expo/background.cjs +14 -14
  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 -6
  65. package/dist/wallet/expo/background.js.map +1 -1
  66. package/dist/wallet/expo/index.cjs +19 -14
  67. package/dist/wallet/expo/index.cjs.map +1 -1
  68. package/dist/wallet/expo/index.d.cts +8 -6
  69. package/dist/wallet/expo/index.d.ts +8 -6
  70. package/dist/wallet/expo/index.js +10 -5
  71. package/dist/wallet/expo/index.js.map +1 -1
  72. package/dist/wallet-BQHNgc_v.d.ts +1874 -0
  73. package/dist/wallet-D9dvXu-K.d.cts +1874 -0
  74. package/dist/worker/expo/index.cjs +9 -9
  75. package/dist/worker/expo/index.d.cts +6 -6
  76. package/dist/worker/expo/index.d.ts +6 -6
  77. package/dist/worker/expo/index.js +5 -5
  78. package/package.json +10 -11
  79. package/dist/chunk-3TCWQPAC.js.map +0 -1
  80. package/dist/chunk-7OL2P5TV.cjs.map +0 -1
  81. package/dist/chunk-FQCMEAWN.js.map +0 -1
  82. package/dist/chunk-J2K7WWMJ.js.map +0 -1
  83. package/dist/chunk-JFZBKPSV.js.map +0 -1
  84. package/dist/chunk-PKD5VPYX.cjs.map +0 -1
  85. package/dist/chunk-PO2ZDZZC.cjs.map +0 -1
  86. package/dist/chunk-R4KRWX7R.js.map +0 -1
  87. package/dist/chunk-V4JZOFYT.cjs.map +0 -1
  88. package/dist/chunk-VMVHRKXM.cjs.map +0 -1
  89. package/dist/chunk-VTXMC4N7.cjs.map +0 -1
  90. package/dist/chunk-Z3QM5XFY.js.map +0 -1
  91. package/dist/hdWalletCapable-D1ImQqlu.d.cts +0 -2214
  92. package/dist/hdWalletCapable-DvumVrKW.d.ts +0 -2214
  93. package/dist/index-DleNQcz5.d.cts +0 -850
  94. package/dist/index-QVhQgYvU.d.ts +0 -850
package/README.md CHANGED
@@ -31,6 +31,10 @@ const identity = MnemonicIdentity.fromMnemonic(mnemonic)
31
31
  const wallet = await Wallet.create({ identity }) // defaults to mainnet
32
32
  ```
33
33
 
34
+ `Wallet.create` also accepts short spellings: `service` in place of the
35
+ `arkProvider`/`indexerProvider`/`onchainProvider` trio, `mode` for `walletMode` and
36
+ `settlement` for `settlementConfig`. Setting both spellings of one field throws.
37
+
34
38
  ### Read-Only Wallets (Watch-Only)
35
39
 
36
40
  The SDK supports read-only wallets that allow you to query wallet state without exposing private keys. This is useful for:
@@ -188,6 +192,7 @@ import {
188
192
  BatchSignableIdentity,
189
193
  SignRequest,
190
194
  isBatchSignable,
195
+ Transaction,
191
196
  Wallet
192
197
  } from '@arkade-os/sdk'
193
198
 
@@ -249,7 +254,7 @@ Wallets read onchain state (UTXOs, transactions, fee rates, chain tip) through a
249
254
 
250
255
  If you don't pass a provider explicitly, `OnchainWallet` and `Wallet.create({ ... })` both default to `EsploraProvider` pointing at the URL in `ESPLORA_URL[networkName]`.
251
256
 
252
- > **New:** the interface also requires `getRawTransaction(txid): Promise<Uint8Array>`, the raw wire bytes of a transaction. Emulator v0.0.7+ demands the previous transaction of every input a covenant spend or intent proof carries, and a boarding or commitment parent has no off-chain source. Both shipped providers implement it; a custom `OnchainProvider` has to add it.
257
+ > **Note:** the interface requires `getRawTransaction(txid): Promise<Uint8Array>`, the raw wire bytes of a transaction. Emulator v0.0.7+ demands the previous transaction of every input a covenant spend or intent proof carries, and a boarding or commitment parent has no off-chain source. Both shipped providers implement it; a custom `OnchainProvider` has to add it.
253
258
 
254
259
  #### Default URLs
255
260
 
@@ -306,6 +311,8 @@ const wallet = await OnchainWallet.create(identity, 'bitcoin', provider)
306
311
  await provider.close()
307
312
  ```
308
313
 
314
+ 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.
315
+
309
316
  #### Atomic 1P1C package broadcast (TRUC / BIP 431)
310
317
 
311
318
  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.
@@ -334,7 +341,7 @@ import { waitForIncomingFunds } from '@arkade-os/sdk'
334
341
  const arkadeAddress = await wallet.getAddress()
335
342
  const boardingAddress = await wallet.getBoardingAddress()
336
343
  console.log('Arkade Address:', arkadeAddress)
337
- console.log('Boarding (Mainnet) Address:', boardingAddress)
344
+ console.log('Boarding (onchain) Address:', boardingAddress)
338
345
 
339
346
  const incomingFunds = await waitForIncomingFunds(wallet)
340
347
  if (incomingFunds.type === "vtxo") {
@@ -392,33 +399,37 @@ const boardingTxId = await new Ramps(wallet).onboard();
392
399
  ### Checking Balance
393
400
 
394
401
  ```typescript
395
- // Get detailed balance information
396
402
  const balance = await wallet.getBalance()
397
- console.log('Total Balance:', balance.total)
398
- console.log('Boarding Total:', balance.boarding.total)
399
- console.log('Offchain Available:', balance.available)
400
- console.log('Offchain Settled:', balance.settled)
401
- console.log('Offchain Preconfirmed:', balance.preconfirmed)
402
- console.log('Gated by a contract:', balance.gated) // swap escrow, chiefly
403
- console.log('Locked by an in-flight intent:', balance.intentLocked)
404
- console.log('Recoverable:', balance.recoverable)
405
- console.log('Awaiting recovery:', balance.pendingRecovery)
406
- console.log('Unilaterally exited:', balance.unrolled)
407
- ```
408
-
409
- `settled` and `preconfirmed` are the owned offchain buckets this relationship is
410
- about — `recoverable`, `pendingRecovery` and `unrolled` are the wallet's funds
411
- too, just held under a different predicate. `available` is what generic spending
412
- will actually pick, and the difference between the two is accounted for exactly:
403
+ ```
404
+
405
+ | Field | What it holds |
406
+ | --- | --- |
407
+ | `boarding.confirmed` | Confirmed onchain [boarding](#onboarding) funds, ready to settle into virtual outputs. |
408
+ | `boarding.unconfirmed` | Boarding funds still waiting for an onchain confirmation. |
409
+ | `boarding.total` | `confirmed + unconfirmed`. |
410
+ | `boarding.loaded` | Set on cached and service-worker balances: `false` until this instance has fetched boarding inputs, so a zero may only mean "not loaded" ([Faster startup](#faster-startup-without-changing-existing-reads)). |
411
+ | `settled` | Finalized offchain funds the wallet owns, spendable or not. |
412
+ | `preconfirmed` | Owned offchain funds not yet finalized in a batch. |
413
+ | `available` | What `send` and every other automatic coin selection can spend now. |
414
+ | `gated` | Held by a contract that generic spending refuses (chiefly swap escrow); only that contract can spend it ([Contract Management](#contract-management)). |
415
+ | `pendingSettlement` | Committed to an in-flight settlement intent; back in `available` when the intent ends. Always `0` without `storage.intentRepository`. |
416
+ | `reserved` | Held by [`reserveVtxos`](#holding-vtxos-for-your-own-use) until released. |
417
+ | `recoverable` | Swept or subdust virtual outputs that [`recoverVtxos`](#recovery-reclaim-swept-vtxos) can reclaim; `getRecoverableBalance()` gives what a batch would return today. |
418
+ | `pendingRecovery` | Under a server signer past its cutoff, which the operator no longer co-signs; recovers once swept and re-settled. |
419
+ | `unrolled` | Already [unilaterally exited](#unilateral-exit): onchain behind its CSV timelock, movable only by `Unroll.completeUnroll`. |
420
+ | `total` | Everything the wallet owns: `boarding.total + settled + preconfirmed + recoverable + pendingRecovery + unrolled`. |
421
+ | `assets` | Every [asset](#assets-issue-reissue-burn-send) balance the wallet owns. |
422
+ | `availableAssets` | The subset of `assets` generic spending can move: the asset counterpart of `available`. |
423
+
424
+ `available` is what remains of the owned offchain funds once everything held for a
425
+ cause is taken out:
413
426
 
414
427
  ```text
415
- settled + preconfirmed === available + gated + intentLocked
428
+ settled + preconfirmed === available + gated + pendingSettlement + reserved
416
429
  ```
417
430
 
418
- `unrolled` holds virtual outputs whose unilateral exit already happened: they sit
419
- onchain behind their CSV timelock, so nothing offchain can move them and
420
- `Unroll.completeUnroll` is the only thing that will. They are never `available` and
421
- never `recoverable` — but they are still your money, so they still count in `total`.
431
+ plus one dust carrier when the wallet holds available assets, since an asset moves
432
+ only with the output that carries it.
422
433
 
423
434
  To show "your money, minus what is tied up", subtract from `settled + preconfirmed`
424
435
  — **not from `total`**, which also contains `boarding.total`, `recoverable`,
@@ -426,10 +437,12 @@ To show "your money, minus what is tied up", subtract from `settled + preconfirm
426
437
  from `total` silently drops them from the figure.
427
438
 
428
439
  ```typescript
429
-
430
- // Get virtual outputs (available for offchain spending)
440
+ // Every virtual output the wallet tracks, spendable or not
431
441
  const vtxos = await wallet.getVtxos()
432
442
 
443
+ // Only those automatic coin selection may pick
444
+ const spendable = await wallet.getSpendableVtxos()
445
+
433
446
  // Get boarding inputs
434
447
  const boardingInputs = await wallet.getBoardingUtxos()
435
448
  ```
@@ -527,7 +540,7 @@ const [vtxos, boardingInputs] = await Promise.all([
527
540
  // For settling transactions
528
541
  const settlementTxId = await wallet.settle({
529
542
  inputs: [...vtxos, ...boardingInputs],
530
- // Optional: specify a mainnet output
543
+ // Optional: specify an onchain output
531
544
  outputs: [{
532
545
  address: "bc1p...",
533
546
  amount: 100_000n
@@ -609,7 +622,7 @@ const urgentlyExpiring = await manager.getExpiringVtxos(60_000)
609
622
  A virtual output whose unilateral exit already happened is never offered for renewal, and the
610
623
  exported `isVtxoExpiringSoon` answers `false` for it regardless of its batch expiry: "expiring
611
624
  soon" is a renewal signal, and no batch can take an output that already lives onchain. Its remedy
612
- is `Unroll.completeUnroll`, and its value shows up in `balance.unrolled`.
625
+ is `Unroll.completeUnroll`, and its value shows up in [`balance.unrolled`](#checking-balance).
613
626
 
614
627
  #### Boarding Input Sweep
615
628
 
@@ -691,7 +704,7 @@ console.log('Failed:', delegationResult.failed.length)
691
704
 
692
705
  The `delegate` method groups virtual outputs by expiry date and submits them to the delegation service.
693
706
 
694
- By default, delegation is scheduled at 90% of each virtual output's remaining lifetime.
707
+ By default, delegation is scheduled three operator sessions before the earliest expiry in each group. This is best effort: a delegator outage near expiry can miss it, leaving the virtual output recoverable.
695
708
 
696
709
  You can override this with an explicit date:
697
710
 
@@ -798,6 +811,71 @@ const exitTxid = await new Ramps(wallet).offboard(
798
811
  service-worker and Expo wallets too. It answers with the server's live `ArkadeInfo`, falling back
799
812
  to the snapshot persisted at wallet construction when the server is unreachable.
800
813
 
814
+ ### Concurrent Spending & Coin Reservations
815
+
816
+ By default the wallet runs one spend at a time: a `send` issued while a settle or a collaborative
817
+ exit waits for its batch swap queues until that batch swap finalizes, and an exit with an amount
818
+ spends every spendable VTXO. Opt in to let spends overlap on disjoint VTXOs:
819
+
820
+ ```typescript
821
+ const wallet = await Wallet.create({ identity, arkProvider, concurrentSpending: true })
822
+ ```
823
+
824
+ With `concurrentSpending`:
825
+
826
+ - `send`, `settle` and asset operations hold the wallet lock only while they pick and reserve their
827
+ inputs; submitting the Arkade transaction and waiting for the batch swap run unlocked.
828
+ - `Ramps.offboard(address, fees, amount)` and `offboardExact` spend only the VTXOs that cover the
829
+ amount (soonest expiry first, as `send` picks), so the rest of the balance stays spendable. A
830
+ full exit, and an exit that names its `vtxos`, behave as before.
831
+ - Naming a VTXO that another operation is spending (`send({ selectedVtxos })`,
832
+ `settle({ inputs })`) throws `VtxoReservedError` before anything is submitted. Exits that pick
833
+ their own VTXOs retry the pick.
834
+ - Boarding UTXOs a settle is spending stay in `getBoardingUtxos()` and the boarding balance, but
835
+ a bare `settle()`, `Ramps.onboard` without explicit UTXOs and the boarding poll skip them until
836
+ that settle finishes, and naming one elsewhere throws `VtxoReservedError`.
837
+
838
+ Concurrency only helps when the balance sits in more than one VTXO: a wallet holding a single VTXO
839
+ still has one operation at a time. Renewal and a bare `settle()` merge the VTXOs they spend into
840
+ one output.
841
+
842
+ #### Holding VTXOs for your own use
843
+
844
+ `reserveVtxos` keeps VTXOs out of every pick the SDK makes for you — `send`, `settle()`, exits,
845
+ renewal, payment-router rails and swap funding — until you release them. You can still spend them
846
+ by naming them. It works with or without `concurrentSpending`.
847
+
848
+ ```typescript
849
+ const hold = wallet.reserveVtxos(coinsForThisOrder)
850
+ try {
851
+ await wallet.send({ recipients, selectedVtxos: coinsForThisOrder })
852
+ } finally {
853
+ hold.release()
854
+ }
855
+ ```
856
+
857
+ Held VTXOs stay in `getVtxos()` and in `getBalance().total`, and move from `available` to
858
+ [`reserved`](#checking-balance). Holding a VTXO that is held or being spent throws `VtxoReservedError`. If the SDK had
859
+ already picked a VTXO when you hold it, that spend throws `VtxoReservedError` (`holder: "held"`)
860
+ instead of spending it; exits and renewal pick again or retry on their own.
861
+
862
+ Limits:
863
+
864
+ - Reservations are in memory, per `Wallet` instance. Two processes or two `Wallet` instances on one
865
+ seed do not see each other's reservations; the operator refuses whichever spend loses.
866
+ - Deprecated-signer migration and recovery name the VTXOs they move, so they can spend a held VTXO.
867
+ - Renewal skips a held VTXO even once it is inside the renewal threshold, since renewing would
868
+ change the outpoint you are holding. It logs a warning naming each such VTXO; spend or release a
869
+ long-lived hold before the VTXO expires, or the server sweeps it and it comes back only through
870
+ recovery.
871
+ - `toReadonly()` views and the service worker's balance reads do not see reservations.
872
+ - `reserveVtxos` is on `Wallet`; the service-worker and Expo wallets accept `concurrentSpending`
873
+ but do not expose holds yet.
874
+
875
+ If your app keeps its own reserved-outpoint set, call `reserveVtxos` where you add to it and
876
+ `release()` where you remove, then let the SDK pick: plain `send`, router payments without
877
+ `selectedVtxos`, and exits without `vtxos` all skip what you hold.
878
+
801
879
  ### Talking to the server through the wallet
802
880
 
803
881
  `getArkadeInfo()` is one of three seams that let a plugin work from a wallet alone, never a
@@ -901,7 +979,7 @@ switch (step.type) {
901
979
  const [parent, child] = step.pkg;
902
980
  console.log(`Parent: ${parent}`)
903
981
  console.log(`Child: ${child}`)
904
- await step.do(); // broadcast the 1C1P package
982
+ await step.do(); // broadcast the 1P1C package
905
983
  break;
906
984
  case Unroll.StepType.DONE:
907
985
  console.log(`Unrolling complete for VTXO ${step.vtxoTxid}`);
@@ -946,7 +1024,7 @@ The flow is **quote → fund → prepare → execute**:
946
1024
  ```typescript
947
1025
  import { UnilateralExit, OnchainWallet, serializeExitPackage } from '@arkade-os/sdk'
948
1026
 
949
- const onchainWallet = await OnchainWallet.create(identity, 'mainnet')
1027
+ const onchainWallet = await OnchainWallet.create(identity, 'bitcoin')
950
1028
 
951
1029
  // 1. Quote: how many txs, how many sats (no funds needed, nothing signed)
952
1030
  const quote = await UnilateralExit.estimate({
@@ -1064,7 +1142,8 @@ const bus = new MessageBus(walletRepo, contractRepo, {
1064
1142
  tickIntervalMs: 10_000, // default 10s
1065
1143
  // Optional, same opt-in as `storage.intentRepository` on a main-thread
1066
1144
  // wallet. Omit it and the worker persists no settlement intent, reconciles
1067
- // none on restart, and counts intent-locked VTXOs as available.
1145
+ // none on restart, and counts intent-locked VTXOs as available
1146
+ // (`pendingSettlement` stays 0).
1068
1147
  intentRepository: new IndexedDBIntentRepository(),
1069
1148
  })
1070
1149
 
@@ -1091,6 +1170,37 @@ const balance = await wallet.getBalance()
1091
1170
  For watch-only wallets, use `ServiceWorkerReadonlyWallet` with a
1092
1171
  `ReadonlySingleKey` identity instead.
1093
1172
 
1173
+ ### Faster startup without changing existing reads
1174
+
1175
+ Existing `Wallet` and `ReadonlyWallet` reads (`getVtxos`, `getBalance`, and
1176
+ `getTransactionHistory`) still synchronize before answering. Retryable provider
1177
+ failures retain the existing offline fallback; terminal errors still reject.
1178
+ Contract initialization still awaits synchronization and watcher startup by default.
1179
+
1180
+ For a returning wallet with persisted contracts, opt into `lazyInitialization: true`
1181
+ and use `getStoredVtxos()` or `getStoredBalance()` (same [fields](#checking-balance) as `getBalance()`) for the initial display. These
1182
+ methods return cached data and request background synchronization. They can return
1183
+ empty or stale data, so inspect `(await wallet.getContractManager()).getSyncState()`:
1184
+ an absent `lastSyncedAt` means no successful sync this session, `syncing` means work
1185
+ is in flight, and `degraded` means refresh failed. Await `manager.whenBooted()` and
1186
+ inspect that state again before treating initialization as successful. Refresh the
1187
+ display after completion; cached reads do not guarantee a change event.
1188
+
1189
+ `getStoredBalance().boarding.loaded` is false until this wallet instance has fetched
1190
+ boarding inputs. Call `getBoardingUtxos()` to refresh them. Stored methods are for
1191
+ display, not transaction inputs; use the normal reads and spending APIs for actions.
1192
+ New contract registration, identity setup, and server-info validation can still
1193
+ require provider calls even with lazy initialization enabled.
1194
+
1195
+ Service-worker factories expose the same `lazyInitialization` option and a separate
1196
+ `lazyBoarding: true` option. Lazy boarding defers the explorer-backed boarding and
1197
+ history refresh, reports `boarding.loaded: false`, and broadcasts `UTXO_UPDATE`
1198
+ when boarding loading completes. Both options default to false and survive worker
1199
+ reinitialization. Existing `GET_BOARDING_UTXOS` requests remain live reads.
1200
+
1201
+ Boarding addresses are fetched concurrently in both modes, so wallets with rotated
1202
+ addresses benefit without opting into changed initialization timing.
1203
+
1094
1204
  ### Worker Architecture
1095
1205
 
1096
1206
  The _worker_ captures the background processing infrastructure for the SDK.
@@ -1111,6 +1221,33 @@ examples.
1111
1221
  Use repository implementations via `StorageConfig`. If you omit `storage`, the
1112
1222
  SDK uses IndexedDB repositories with the default database name.
1113
1223
 
1224
+ #### Paged repository reads
1225
+
1226
+ Collection repositories expose `getVtxosPage`, `getVtxosForScriptPage`, `getUtxosPage`,
1227
+ `getTransactionHistoryPage`, `getContractsPage`, and `getIntentsPage`.
1228
+ Each accepts `{ limit, after? }` and returns
1229
+ `{ items, nextCursor? }`; pass `nextCursor` as `after` until it is absent.
1230
+ Limits must be 1–500. History pages are ordered by `(createdAt, transaction key)`;
1231
+ `since` is inclusive Unix milliseconds. Contract and intent pages are ordered
1232
+ by their script and intent transaction ID. Filters must stay the same across
1233
+ pages. Repository collection reads use pages directly; `collectVtxos`,
1234
+ `collectScriptVtxos`, `collectUtxos`, `collectTransactionHistory`, `collectContracts`, and
1235
+ `collectIntents` traverse all pages when a complete snapshot is required.
1236
+ Wallet spending paths consume every VTXO or UTXO page before coin selection.
1237
+ In `@arkade-os/swap`, RFQ page `since` values use Unix seconds instead.
1238
+
1239
+ ```ts
1240
+ let after;
1241
+ do {
1242
+ const page = await walletRepo.getTransactionHistoryPage(
1243
+ { address, since: Date.now() - 86_400_000 },
1244
+ { limit: 100, after },
1245
+ );
1246
+ for (const transaction of page.items) handle(transaction);
1247
+ after = page.nextCursor;
1248
+ } while (after);
1249
+ ```
1250
+
1114
1251
  #### Repository Versioning
1115
1252
 
1116
1253
  `WalletRepository`, `ContractRepository`, `IntentRepository`,
@@ -1124,7 +1261,7 @@ version is bumped, signaling that a semantic update is required:
1124
1261
  import { WalletRepository } from '@arkade-os/sdk'
1125
1262
 
1126
1263
  class MyWalletRepository implements WalletRepository {
1127
- readonly version = 1 // must match the interface's literal type
1264
+ readonly version = 2 // must match the interface's literal type
1128
1265
  // ...
1129
1266
  }
1130
1267
  ```
@@ -1134,17 +1271,19 @@ class MyWalletRepository implements WalletRepository {
1134
1271
  For Node.js or React Native environments, use the SQLite repository with any
1135
1272
  SQLite driver. The SDK accepts a `SQLExecutor` interface — you provide the
1136
1273
  driver, the SDK handles the schema.
1274
+ Paged wallet reads require SQLite 3.15.0 or newer because they use
1275
+ [row-value comparisons](https://sqlite.org/rowvalue.html#backwards_compatibility).
1137
1276
 
1138
1277
  See [examples/node/multiple-wallets.ts](examples/node/multiple-wallets.ts) for
1139
- a full working example using `better-sqlite3`.
1278
+ a full working example using `node:sqlite`.
1140
1279
 
1141
1280
  ```typescript
1142
1281
  import { MnemonicIdentity, Wallet } from '@arkade-os/sdk'
1143
1282
  import { SQLiteWalletRepository, SQLiteContractRepository, SQLExecutor } from '@arkade-os/sdk/repositories/sqlite'
1144
- import Database from 'better-sqlite3'
1283
+ import { DatabaseSync } from 'node:sqlite'
1145
1284
 
1146
- const db = new Database('my-wallet.sqlite')
1147
- db.pragma('journal_mode = WAL')
1285
+ const db = new DatabaseSync('my-wallet.sqlite')
1286
+ db.exec('PRAGMA journal_mode = WAL')
1148
1287
 
1149
1288
  const executor: SQLExecutor = {
1150
1289
  run: async (sql, params) => { db.prepare(sql).run(...(params ?? [])) },
@@ -1227,7 +1366,6 @@ const wallet = await Wallet.create({
1227
1366
  })
1228
1367
  ```
1229
1368
 
1230
- ### Using with Node.js
1231
1369
  ### Using with Node.js
1232
1370
 
1233
1371
  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:
@@ -1347,6 +1485,47 @@ When you call `wallet.notifyIncomingFunds()` or use `waitForIncomingFunds()`, it
1347
1485
 
1348
1486
  `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.
1349
1487
 
1488
+ To watch transactions at scripts you don't know in advance (extension packets at someone else's offer, say), `indexerProvider.getFilteredSubscription(filter, signal)` streams what a server-side CEL filter matches. It yields `SubscriptionEvent | SubscriptionHeartbeat`, so check `type === "event"` before reading `tx`. A custom `IndexerProvider` must implement it.
1489
+
1490
+ #### Compiler artifacts
1491
+
1492
+ 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; wallet, artifact JSON and matching digest/preimage values are already available.
1493
+
1494
+ 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.
1495
+
1496
+ 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.
1497
+
1498
+ 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.
1499
+
1500
+ ```typescript
1501
+ import { arkade, Arkade, ArkAddress } from '@arkade-os/sdk'
1502
+
1503
+ const program = arkade.programFromArtifact(artifactJson)
1504
+ const client = await Arkade.connect({
1505
+ service: { network: 'bitcoin' }, // operator, indexer, esplora and covenant co-signer
1506
+ identity: wallet.identity,
1507
+ contractManager: await wallet.getContractManager(),
1508
+ })
1509
+ const contract = client.contract(program, { digest })
1510
+ await contract.register({ label: 'Hashlock' })
1511
+
1512
+ const funded = await contract.on('vtxo_received', async () => {
1513
+ funded.off()
1514
+ const { txid } = await contract.functions.claim(preimage)
1515
+ .to(ArkAddress.decode(recipientAddress).pkScript, 10_000n)
1516
+ .send()
1517
+ })
1518
+ await wallet.send({ address: contract.address, amount: 10_000 })
1519
+ ```
1520
+
1521
+ `service` takes `{ network }` for the reference deployment or `{ operatorUrl, emulatorUrl? }` for a custom one, whose covenant paths stay disabled without `emulatorUrl`; passing the `arkade`/`emulator`/`indexer` providers yourself still works. Without a wallet, pass `storage: { contractRepository }` instead of `contractManager`: the client then owns a session-scoped manager, which `client.dispose()` stops.
1522
+
1523
+ `contract.on('vtxo_received' | 'vtxo_spent' | 'connection_reset', cb)` reports activity from subscription on and returns a handle with `off()`. Coins that predate it are not replayed and a reconnect re-syncs silently, so read `contract.getVtxos()` (every stored VTXO, spent ones included) after subscribing and on each `connection_reset`.
1524
+
1525
+ 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.
1526
+
1527
+ 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.
1528
+
1350
1529
  #### HD look-ahead window
1351
1530
 
1352
1531
  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.
@@ -1424,7 +1603,7 @@ const contract = await manager.createContract({
1424
1603
  })
1425
1604
 
1426
1605
  // Listen for all contracts events (wallet address + external contracts)
1427
- const unsubscribe = await manager.onContractEvent((event) => {
1606
+ const unsubscribe = manager.onContractEvent((event) => {
1428
1607
  switch (event.type) {
1429
1608
  case 'vtxo_received':
1430
1609
  console.log(`Received ${event.vtxos.length} virtual outputs to ${event.contractScript}`)
@@ -1441,7 +1620,7 @@ await manager.updateContractParams(contract.script, { preimage: revealedPreimage
1441
1620
  // Check spendable paths (requires a specific virtual output)
1442
1621
  const [withVtxos] = await manager.getContractsWithVtxos({ script: contract.script })
1443
1622
  const vtxo = withVtxos.vtxos[0]
1444
- const paths = manager.getSpendablePaths({
1623
+ const paths = await manager.getSpendablePaths({
1445
1624
  contractScript: contract.script,
1446
1625
  vtxo,
1447
1626
  collaborative: true,
@@ -1497,14 +1676,16 @@ versions keep the coverage they have today.
1497
1676
  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).
1498
1677
 
1499
1678
  ```typescript
1679
+ import { collectContracts, collectTransactionHistory, collectUtxos, collectVtxos } from '@arkade-os/sdk'
1680
+
1500
1681
  // Wallet repository — VTXOs, UTXOs, transaction history, settings
1501
1682
  const addr = await wallet.getAddress()
1502
- const vtxos = await wallet.walletRepository.getVtxos(addr)
1503
- const utxos = await wallet.walletRepository.getUtxos(addr)
1504
- const history = await wallet.walletRepository.getTransactionHistory(addr)
1683
+ const vtxos = await collectVtxos(wallet.walletRepository, addr)
1684
+ const utxos = await collectUtxos(wallet.walletRepository, addr)
1685
+ const history = await collectTransactionHistory(wallet.walletRepository, addr)
1505
1686
 
1506
1687
  // Contract repository — script-keyed contracts (default address, VHTLCs, etc.)
1507
- const contracts = await wallet.contractRepository.getContracts({ type: 'vhtlc' })
1688
+ const contracts = await collectContracts(wallet.contractRepository, { type: 'vhtlc' })
1508
1689
  await wallet.contractRepository.saveContract(myContract)
1509
1690
  await wallet.contractRepository.deleteContract(myContract.script)
1510
1691
  ```
@@ -1518,15 +1699,15 @@ This package is developed inside the [arkade-os/ts-sdk](../..) monorepo. See the
1518
1699
  Common package-local commands (run from the repo root):
1519
1700
 
1520
1701
  ```bash
1521
- pnpm -C packages/ts-sdk run typecheck # Type-check the SDK
1522
- pnpm -C packages/ts-sdk test:unit # Unit tests, excluding e2e
1523
- pnpm -C packages/ts-sdk test:watch # Vitest watch mode
1524
- pnpm -C packages/ts-sdk test:coverage # Coverage report
1525
- pnpm -C packages/ts-sdk docs:build # Build TypeDoc API docs
1526
- pnpm -C packages/ts-sdk docs:open # Open API docs in the browser
1702
+ pnpm -C packages/sdk run typecheck # Type-check the SDK
1703
+ pnpm -C packages/sdk test:unit # Unit tests, excluding e2e
1704
+ pnpm -C packages/sdk test:watch # Vitest watch mode
1705
+ pnpm -C packages/sdk test:coverage # Coverage report
1706
+ pnpm -C packages/sdk docs:build # Build TypeDoc API docs
1707
+ pnpm -C packages/sdk docs:open # Open API docs in the browser
1527
1708
  ```
1528
1709
 
1529
- For integration tests, use the root commands (`pnpm run test:integration:ts-sdk` and the `regtest:*:ts-sdk` family) — see the root README.
1710
+ For integration tests, use the root commands (`pnpm run test:integration:sdk` and the `regtest:*:sdk` family) — see [CONTRIBUTING.md](../../CONTRIBUTING.md).
1530
1711
 
1531
1712
  ### Releasing
1532
1713
 
@@ -1,18 +1,18 @@
1
1
  'use strict';
2
2
 
3
- var chunk7OL2P5TV_cjs = require('../chunk-7OL2P5TV.cjs');
4
- require('../chunk-V4JZOFYT.cjs');
5
- require('../chunk-VTXMC4N7.cjs');
3
+ var chunkGQUFPTN7_cjs = require('../chunk-GQUFPTN7.cjs');
4
+ require('../chunk-HQNQ3CLQ.cjs');
5
+ require('../chunk-XLHUGP3G.cjs');
6
6
 
7
7
 
8
8
 
9
9
  Object.defineProperty(exports, "ExpoArkProvider", {
10
10
  enumerable: true,
11
- get: function () { return chunk7OL2P5TV_cjs.ExpoArkProvider; }
11
+ get: function () { return chunkGQUFPTN7_cjs.ExpoArkProvider; }
12
12
  });
13
13
  Object.defineProperty(exports, "ExpoIndexerProvider", {
14
14
  enumerable: true,
15
- get: function () { return chunk7OL2P5TV_cjs.ExpoIndexerProvider; }
15
+ get: function () { return chunkGQUFPTN7_cjs.ExpoIndexerProvider; }
16
16
  });
17
17
  //# sourceMappingURL=expo.cjs.map
18
18
  //# sourceMappingURL=expo.cjs.map
@@ -1,9 +1,9 @@
1
- import { q as RestArkProvider, n as SettlementEvent, T as TxNotificationEvent, r as RestIndexerProvider, s as SubscriptionResponse } from '../index-BIL9boGL.cjs';
2
- export { t as ArkProvider, u as IndexerProvider } from '../index-BIL9boGL.cjs';
1
+ import { r as RestArkProvider, o as SettlementEvent, T as TxNotificationEvent, s as RestIndexerProvider, t as SubscriptionEvent, u as SubscriptionHeartbeat } from '../index-B3fRWO5i.cjs';
2
+ export { v as ArkProvider, w as IndexerProvider } from '../index-B3fRWO5i.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 { q as RestArkProvider, n as SettlementEvent, T as TxNotificationEvent, r as RestIndexerProvider, s as SubscriptionResponse } from '../index-BIL9boGL.js';
2
- export { t as ArkProvider, u as IndexerProvider } from '../index-BIL9boGL.js';
1
+ import { r as RestArkProvider, o as SettlementEvent, T as TxNotificationEvent, s as RestIndexerProvider, t as SubscriptionEvent, u as SubscriptionHeartbeat } from '../index-B3fRWO5i.js';
2
+ export { v as ArkProvider, w as IndexerProvider } from '../index-B3fRWO5i.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,5 +1,5 @@
1
- export { ExpoArkProvider, ExpoIndexerProvider } from '../chunk-FQCMEAWN.js';
2
- import '../chunk-J2K7WWMJ.js';
3
- import '../chunk-JFZBKPSV.js';
1
+ export { ExpoArkProvider, ExpoIndexerProvider } from '../chunk-FXVEOEYB.js';
2
+ import '../chunk-6MIGYYWB.js';
3
+ import '../chunk-QFB55EOZ.js';
4
4
  //# sourceMappingURL=expo.js.map
5
5
  //# sourceMappingURL=expo.js.map
@@ -1,4 +1,4 @@
1
- import { a as TaskQueue, c as TaskItem, d as TaskResult } from './taskRunner-Cu4uO1to.js';
1
+ import { a as TaskQueue, c as TaskItem, d as TaskResult } from './taskRunner-CdfoCMaw.js';
2
2
 
3
3
  /**
4
4
  * Minimal async key-value storage interface.
@@ -1,4 +1,4 @@
1
- import { a as TaskQueue, c as TaskItem, d as TaskResult } from './taskRunner-DxpSuKmh.cjs';
1
+ import { a as TaskQueue, c as TaskItem, d as TaskResult } from './taskRunner-Dk6oDAtd.cjs';
2
2
 
3
3
  /**
4
4
  * Minimal async key-value storage interface.