@arkade-os/sdk 0.3.13 → 0.4.0-next.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 (270) hide show
  1. package/README.md +586 -54
  2. package/dist/cjs/asset/assetGroup.js +141 -0
  3. package/dist/cjs/asset/assetId.js +88 -0
  4. package/dist/cjs/asset/assetInput.js +204 -0
  5. package/dist/cjs/asset/assetOutput.js +159 -0
  6. package/dist/cjs/asset/assetRef.js +82 -0
  7. package/dist/cjs/asset/index.js +24 -0
  8. package/dist/cjs/asset/metadata.js +172 -0
  9. package/dist/cjs/asset/packet.js +164 -0
  10. package/dist/cjs/asset/types.js +25 -0
  11. package/dist/cjs/asset/utils.js +105 -0
  12. package/dist/cjs/bip322/index.js +270 -0
  13. package/dist/cjs/contracts/arkcontract.js +148 -0
  14. package/dist/cjs/contracts/contractManager.js +436 -0
  15. package/dist/cjs/contracts/contractWatcher.js +567 -0
  16. package/dist/cjs/contracts/handlers/default.js +85 -0
  17. package/dist/cjs/contracts/handlers/delegate.js +89 -0
  18. package/dist/cjs/contracts/handlers/helpers.js +105 -0
  19. package/dist/cjs/contracts/handlers/index.js +19 -0
  20. package/dist/cjs/contracts/handlers/registry.js +89 -0
  21. package/dist/cjs/contracts/handlers/vhtlc.js +193 -0
  22. package/dist/cjs/contracts/index.js +41 -0
  23. package/dist/cjs/contracts/types.js +2 -0
  24. package/dist/cjs/forfeit.js +12 -8
  25. package/dist/cjs/identity/index.js +1 -0
  26. package/dist/cjs/identity/seedIdentity.js +255 -0
  27. package/dist/cjs/index.js +72 -14
  28. package/dist/cjs/intent/index.js +47 -11
  29. package/dist/cjs/providers/ark.js +7 -0
  30. package/dist/cjs/providers/delegator.js +66 -0
  31. package/dist/cjs/providers/expoIndexer.js +5 -0
  32. package/dist/cjs/providers/indexer.js +68 -1
  33. package/dist/cjs/providers/utils.js +1 -0
  34. package/dist/cjs/repositories/contractRepository.js +0 -103
  35. package/dist/cjs/repositories/inMemory/contractRepository.js +55 -0
  36. package/dist/cjs/repositories/inMemory/walletRepository.js +80 -0
  37. package/dist/cjs/repositories/index.js +16 -0
  38. package/dist/cjs/repositories/indexedDB/contractRepository.js +187 -0
  39. package/dist/cjs/repositories/indexedDB/db.js +19 -0
  40. package/dist/cjs/repositories/indexedDB/manager.js +97 -0
  41. package/dist/cjs/repositories/indexedDB/schema.js +159 -0
  42. package/dist/cjs/repositories/indexedDB/walletRepository.js +338 -0
  43. package/dist/cjs/repositories/indexedDB/websqlAdapter.js +144 -0
  44. package/dist/cjs/repositories/migrations/contractRepositoryImpl.js +127 -0
  45. package/dist/cjs/repositories/migrations/fromStorageAdapter.js +66 -0
  46. package/dist/cjs/repositories/migrations/walletRepositoryImpl.js +180 -0
  47. package/dist/cjs/repositories/realm/contractRepository.js +120 -0
  48. package/dist/cjs/repositories/realm/index.js +9 -0
  49. package/dist/cjs/repositories/realm/schemas.js +108 -0
  50. package/dist/cjs/repositories/realm/types.js +7 -0
  51. package/dist/cjs/repositories/realm/walletRepository.js +273 -0
  52. package/dist/cjs/repositories/serialization.js +49 -0
  53. package/dist/cjs/repositories/sqlite/contractRepository.js +139 -0
  54. package/dist/cjs/repositories/sqlite/index.js +7 -0
  55. package/dist/cjs/repositories/sqlite/types.js +2 -0
  56. package/dist/cjs/repositories/sqlite/walletRepository.js +328 -0
  57. package/dist/cjs/repositories/walletRepository.js +0 -169
  58. package/dist/cjs/script/base.js +54 -0
  59. package/dist/cjs/script/delegate.js +49 -0
  60. package/dist/cjs/storage/asyncStorage.js +4 -1
  61. package/dist/cjs/storage/fileSystem.js +3 -0
  62. package/dist/cjs/storage/inMemory.js +3 -0
  63. package/dist/cjs/storage/indexedDB.js +5 -1
  64. package/dist/cjs/storage/localStorage.js +3 -0
  65. package/dist/cjs/utils/arkTransaction.js +16 -0
  66. package/dist/cjs/utils/transactionHistory.js +50 -0
  67. package/dist/cjs/wallet/asset-manager.js +338 -0
  68. package/dist/cjs/wallet/asset.js +117 -0
  69. package/dist/cjs/wallet/batch.js +1 -1
  70. package/dist/cjs/wallet/delegator.js +235 -0
  71. package/dist/cjs/wallet/expo/background.js +133 -0
  72. package/dist/cjs/wallet/expo/index.js +9 -0
  73. package/dist/cjs/wallet/expo/wallet.js +231 -0
  74. package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +568 -0
  75. package/dist/cjs/wallet/serviceWorker/wallet.js +383 -102
  76. package/dist/cjs/wallet/utils.js +58 -0
  77. package/dist/cjs/wallet/validation.js +151 -0
  78. package/dist/cjs/wallet/vtxo-manager.js +8 -1
  79. package/dist/cjs/wallet/wallet.js +702 -260
  80. package/dist/cjs/worker/browser/service-worker-manager.js +82 -0
  81. package/dist/cjs/{wallet/serviceWorker → worker/browser}/utils.js +2 -1
  82. package/dist/cjs/worker/expo/asyncStorageTaskQueue.js +78 -0
  83. package/dist/cjs/worker/expo/index.js +12 -0
  84. package/dist/cjs/worker/expo/processors/contractPollProcessor.js +61 -0
  85. package/dist/cjs/worker/expo/processors/index.js +6 -0
  86. package/dist/cjs/worker/expo/taskQueue.js +41 -0
  87. package/dist/cjs/worker/expo/taskRunner.js +57 -0
  88. package/dist/cjs/worker/messageBus.js +252 -0
  89. package/dist/esm/asset/assetGroup.js +137 -0
  90. package/dist/esm/asset/assetId.js +84 -0
  91. package/dist/esm/asset/assetInput.js +199 -0
  92. package/dist/esm/asset/assetOutput.js +154 -0
  93. package/dist/esm/asset/assetRef.js +78 -0
  94. package/dist/esm/asset/index.js +8 -0
  95. package/dist/esm/asset/metadata.js +167 -0
  96. package/dist/esm/asset/packet.js +159 -0
  97. package/dist/esm/asset/types.js +22 -0
  98. package/dist/esm/asset/utils.js +99 -0
  99. package/dist/esm/bip322/index.js +267 -0
  100. package/dist/esm/contracts/arkcontract.js +141 -0
  101. package/dist/esm/contracts/contractManager.js +432 -0
  102. package/dist/esm/contracts/contractWatcher.js +563 -0
  103. package/dist/esm/contracts/handlers/default.js +82 -0
  104. package/dist/esm/contracts/handlers/delegate.js +86 -0
  105. package/dist/esm/contracts/handlers/helpers.js +66 -0
  106. package/dist/esm/contracts/handlers/index.js +12 -0
  107. package/dist/esm/contracts/handlers/registry.js +86 -0
  108. package/dist/esm/contracts/handlers/vhtlc.js +190 -0
  109. package/dist/esm/contracts/index.js +13 -0
  110. package/dist/esm/contracts/types.js +1 -0
  111. package/dist/esm/forfeit.js +11 -8
  112. package/dist/esm/identity/index.js +1 -0
  113. package/dist/esm/identity/seedIdentity.js +249 -0
  114. package/dist/esm/index.js +28 -15
  115. package/dist/esm/intent/index.js +44 -9
  116. package/dist/esm/providers/ark.js +7 -0
  117. package/dist/esm/providers/delegator.js +62 -0
  118. package/dist/esm/providers/expoIndexer.js +5 -0
  119. package/dist/esm/providers/indexer.js +68 -1
  120. package/dist/esm/providers/utils.js +1 -0
  121. package/dist/esm/repositories/contractRepository.js +1 -101
  122. package/dist/esm/repositories/inMemory/contractRepository.js +51 -0
  123. package/dist/esm/repositories/inMemory/walletRepository.js +76 -0
  124. package/dist/esm/repositories/index.js +8 -0
  125. package/dist/esm/repositories/indexedDB/contractRepository.js +183 -0
  126. package/dist/esm/repositories/indexedDB/db.js +4 -0
  127. package/dist/esm/repositories/indexedDB/manager.js +92 -0
  128. package/dist/esm/repositories/indexedDB/schema.js +155 -0
  129. package/dist/esm/repositories/indexedDB/walletRepository.js +334 -0
  130. package/dist/esm/repositories/indexedDB/websqlAdapter.js +138 -0
  131. package/dist/esm/repositories/migrations/contractRepositoryImpl.js +121 -0
  132. package/dist/esm/repositories/migrations/fromStorageAdapter.js +58 -0
  133. package/dist/esm/repositories/migrations/walletRepositoryImpl.js +176 -0
  134. package/dist/esm/repositories/realm/contractRepository.js +116 -0
  135. package/dist/esm/repositories/realm/index.js +3 -0
  136. package/dist/esm/repositories/realm/schemas.js +105 -0
  137. package/dist/esm/repositories/realm/types.js +6 -0
  138. package/dist/esm/repositories/realm/walletRepository.js +269 -0
  139. package/dist/esm/repositories/serialization.js +40 -0
  140. package/dist/esm/repositories/sqlite/contractRepository.js +135 -0
  141. package/dist/esm/repositories/sqlite/index.js +2 -0
  142. package/dist/esm/repositories/sqlite/types.js +1 -0
  143. package/dist/esm/repositories/sqlite/walletRepository.js +324 -0
  144. package/dist/esm/repositories/walletRepository.js +1 -167
  145. package/dist/esm/script/base.js +21 -1
  146. package/dist/esm/script/delegate.js +46 -0
  147. package/dist/esm/storage/asyncStorage.js +4 -1
  148. package/dist/esm/storage/fileSystem.js +3 -0
  149. package/dist/esm/storage/inMemory.js +3 -0
  150. package/dist/esm/storage/indexedDB.js +5 -1
  151. package/dist/esm/storage/localStorage.js +3 -0
  152. package/dist/esm/utils/arkTransaction.js +15 -0
  153. package/dist/esm/utils/transactionHistory.js +50 -0
  154. package/dist/esm/wallet/asset-manager.js +333 -0
  155. package/dist/esm/wallet/asset.js +111 -0
  156. package/dist/esm/wallet/batch.js +1 -1
  157. package/dist/esm/wallet/delegator.js +231 -0
  158. package/dist/esm/wallet/expo/background.js +128 -0
  159. package/dist/esm/wallet/expo/index.js +2 -0
  160. package/dist/esm/wallet/expo/wallet.js +194 -0
  161. package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +564 -0
  162. package/dist/esm/wallet/serviceWorker/wallet.js +382 -101
  163. package/dist/esm/wallet/utils.js +54 -0
  164. package/dist/esm/wallet/validation.js +139 -0
  165. package/dist/esm/wallet/vtxo-manager.js +8 -1
  166. package/dist/esm/wallet/wallet.js +704 -229
  167. package/dist/esm/worker/browser/service-worker-manager.js +76 -0
  168. package/dist/esm/{wallet/serviceWorker → worker/browser}/utils.js +2 -1
  169. package/dist/esm/worker/expo/asyncStorageTaskQueue.js +74 -0
  170. package/dist/esm/worker/expo/index.js +4 -0
  171. package/dist/esm/worker/expo/processors/contractPollProcessor.js +58 -0
  172. package/dist/esm/worker/expo/processors/index.js +1 -0
  173. package/dist/esm/worker/expo/taskQueue.js +37 -0
  174. package/dist/esm/worker/expo/taskRunner.js +54 -0
  175. package/dist/esm/worker/messageBus.js +248 -0
  176. package/dist/types/asset/assetGroup.d.ts +28 -0
  177. package/dist/types/asset/assetId.d.ts +19 -0
  178. package/dist/types/asset/assetInput.d.ts +46 -0
  179. package/dist/types/asset/assetOutput.d.ts +39 -0
  180. package/dist/types/asset/assetRef.d.ts +25 -0
  181. package/dist/types/asset/index.d.ts +8 -0
  182. package/dist/types/asset/metadata.d.ts +37 -0
  183. package/dist/types/asset/packet.d.ts +27 -0
  184. package/dist/types/asset/types.d.ts +18 -0
  185. package/dist/types/asset/utils.d.ts +21 -0
  186. package/dist/types/bip322/index.d.ts +55 -0
  187. package/dist/types/contracts/arkcontract.d.ts +101 -0
  188. package/dist/types/contracts/contractManager.d.ts +331 -0
  189. package/dist/types/contracts/contractWatcher.d.ts +192 -0
  190. package/dist/types/contracts/handlers/default.d.ts +19 -0
  191. package/dist/types/contracts/handlers/delegate.d.ts +21 -0
  192. package/dist/types/contracts/handlers/helpers.d.ts +18 -0
  193. package/dist/types/contracts/handlers/index.d.ts +7 -0
  194. package/dist/types/contracts/handlers/registry.d.ts +65 -0
  195. package/dist/types/contracts/handlers/vhtlc.d.ts +32 -0
  196. package/dist/types/contracts/index.d.ts +14 -0
  197. package/dist/types/contracts/types.d.ts +222 -0
  198. package/dist/types/forfeit.d.ts +2 -1
  199. package/dist/types/identity/index.d.ts +1 -0
  200. package/dist/types/identity/seedIdentity.d.ts +128 -0
  201. package/dist/types/index.d.ts +22 -12
  202. package/dist/types/intent/index.d.ts +15 -1
  203. package/dist/types/providers/ark.d.ts +11 -2
  204. package/dist/types/providers/delegator.d.ts +29 -0
  205. package/dist/types/providers/indexer.d.ts +11 -1
  206. package/dist/types/repositories/contractRepository.d.ts +30 -19
  207. package/dist/types/repositories/inMemory/contractRepository.d.ts +17 -0
  208. package/dist/types/repositories/inMemory/walletRepository.d.ts +26 -0
  209. package/dist/types/repositories/index.d.ts +7 -0
  210. package/dist/types/repositories/indexedDB/contractRepository.d.ts +21 -0
  211. package/dist/types/repositories/indexedDB/db.d.ts +4 -0
  212. package/dist/types/repositories/indexedDB/manager.d.ts +22 -0
  213. package/dist/types/repositories/indexedDB/schema.d.ts +8 -0
  214. package/dist/types/repositories/indexedDB/walletRepository.d.ts +25 -0
  215. package/dist/types/repositories/indexedDB/websqlAdapter.d.ts +49 -0
  216. package/dist/types/repositories/migrations/contractRepositoryImpl.d.ts +24 -0
  217. package/dist/types/repositories/migrations/fromStorageAdapter.d.ts +19 -0
  218. package/dist/types/repositories/migrations/walletRepositoryImpl.d.ts +27 -0
  219. package/dist/types/repositories/realm/contractRepository.d.ts +24 -0
  220. package/dist/types/repositories/realm/index.d.ts +4 -0
  221. package/dist/types/repositories/realm/schemas.d.ts +208 -0
  222. package/dist/types/repositories/realm/types.d.ts +16 -0
  223. package/dist/types/repositories/realm/walletRepository.d.ts +31 -0
  224. package/dist/types/repositories/serialization.d.ts +40 -0
  225. package/dist/types/repositories/sqlite/contractRepository.d.ts +33 -0
  226. package/dist/types/repositories/sqlite/index.d.ts +3 -0
  227. package/dist/types/repositories/sqlite/types.d.ts +18 -0
  228. package/dist/types/repositories/sqlite/walletRepository.d.ts +40 -0
  229. package/dist/types/repositories/walletRepository.d.ts +13 -24
  230. package/dist/types/script/base.d.ts +1 -0
  231. package/dist/types/script/delegate.d.ts +36 -0
  232. package/dist/types/storage/asyncStorage.d.ts +4 -0
  233. package/dist/types/storage/fileSystem.d.ts +3 -0
  234. package/dist/types/storage/inMemory.d.ts +3 -0
  235. package/dist/types/storage/index.d.ts +3 -0
  236. package/dist/types/storage/indexedDB.d.ts +3 -0
  237. package/dist/types/storage/localStorage.d.ts +3 -0
  238. package/dist/types/utils/arkTransaction.d.ts +6 -0
  239. package/dist/types/wallet/asset-manager.d.ts +78 -0
  240. package/dist/types/wallet/asset.d.ts +21 -0
  241. package/dist/types/wallet/batch.d.ts +1 -1
  242. package/dist/types/wallet/delegator.d.ts +24 -0
  243. package/dist/types/wallet/expo/background.d.ts +66 -0
  244. package/dist/types/wallet/expo/index.d.ts +4 -0
  245. package/dist/types/wallet/expo/wallet.d.ts +97 -0
  246. package/dist/types/wallet/index.d.ts +75 -2
  247. package/dist/types/wallet/serviceWorker/wallet-message-handler.d.ts +366 -0
  248. package/dist/types/wallet/serviceWorker/wallet.d.ts +20 -11
  249. package/dist/types/wallet/utils.d.ts +12 -1
  250. package/dist/types/wallet/validation.d.ts +24 -0
  251. package/dist/types/wallet/wallet.d.ts +111 -17
  252. package/dist/types/worker/browser/service-worker-manager.d.ts +21 -0
  253. package/dist/types/{wallet/serviceWorker → worker/browser}/utils.d.ts +2 -1
  254. package/dist/types/worker/expo/asyncStorageTaskQueue.d.ts +46 -0
  255. package/dist/types/worker/expo/index.d.ts +7 -0
  256. package/dist/types/worker/expo/processors/contractPollProcessor.d.ts +14 -0
  257. package/dist/types/worker/expo/processors/index.d.ts +1 -0
  258. package/dist/types/worker/expo/taskQueue.d.ts +50 -0
  259. package/dist/types/worker/expo/taskRunner.d.ts +42 -0
  260. package/dist/types/worker/messageBus.d.ts +109 -0
  261. package/package.json +69 -11
  262. package/dist/cjs/wallet/serviceWorker/request.js +0 -78
  263. package/dist/cjs/wallet/serviceWorker/response.js +0 -222
  264. package/dist/cjs/wallet/serviceWorker/worker.js +0 -655
  265. package/dist/esm/wallet/serviceWorker/request.js +0 -75
  266. package/dist/esm/wallet/serviceWorker/response.js +0 -219
  267. package/dist/esm/wallet/serviceWorker/worker.js +0 -651
  268. package/dist/types/wallet/serviceWorker/request.d.ts +0 -74
  269. package/dist/types/wallet/serviceWorker/response.d.ts +0 -123
  270. package/dist/types/wallet/serviceWorker/worker.d.ts +0 -53
@@ -0,0 +1,567 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ContractWatcher = void 0;
4
+ /**
5
+ * Watches multiple contracts for VTXO changes with resilient connection handling.
6
+ *
7
+ * Features:
8
+ * - Automatic reconnection with exponential backoff
9
+ * - Failsafe polling to catch missed events
10
+ * - Polls immediately after (re)connection to sync state
11
+ * - Graceful handling of subscription failures
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const watcher = new ContractWatcher({
16
+ * indexerProvider: wallet.indexerProvider,
17
+ * });
18
+ *
19
+ * // Add the wallet's default contract
20
+ * await watcher.addContract(defaultContract);
21
+ *
22
+ * // Add additional contracts (swaps, etc.)
23
+ * await watcher.addContract(swapContract);
24
+ *
25
+ * // Start watching for events
26
+ * const stop = await watcher.startWatching((event) => {
27
+ * console.log(`${event.type} on contract ${event.contractScript}`);
28
+ * });
29
+ *
30
+ * // Later: stop watching
31
+ * stop();
32
+ * ```
33
+ */
34
+ class ContractWatcher {
35
+ constructor(config) {
36
+ this.contracts = new Map();
37
+ this.isWatching = false;
38
+ this.connectionState = "disconnected";
39
+ this.reconnectAttempts = 0;
40
+ this.config = {
41
+ failsafePollIntervalMs: 60000, // 1 minute
42
+ reconnectDelayMs: 1000, // 1 second
43
+ maxReconnectDelayMs: 30000, // 30 seconds
44
+ maxReconnectAttempts: 0, // unlimited
45
+ ...config,
46
+ };
47
+ }
48
+ /**
49
+ * Add a contract to be watched.
50
+ *
51
+ * Active contracts are immediately subscribed. All contracts are polled
52
+ * to discover any existing VTXOs (which may cause them to be watched
53
+ * even if inactive).
54
+ */
55
+ async addContract(contract) {
56
+ const state = {
57
+ contract,
58
+ lastKnownVtxos: new Map(),
59
+ };
60
+ this.contracts.set(contract.script, state);
61
+ // If we're already watching, poll to discover VTXOs and update subscription
62
+ if (this.isWatching) {
63
+ // Poll first to discover VTXOs (may affect whether we watch this contract)
64
+ await this.pollContracts([contract.script]);
65
+ // Update subscription based on active state and VTXOs
66
+ await this.tryUpdateSubscription();
67
+ }
68
+ }
69
+ /**
70
+ * Update an existing contract.
71
+ */
72
+ async updateContract(contract) {
73
+ const existing = this.contracts.get(contract.script);
74
+ if (!existing) {
75
+ throw new Error(`Contract ${contract.script} not found`);
76
+ }
77
+ existing.contract = contract;
78
+ if (this.isWatching) {
79
+ await this.tryUpdateSubscription();
80
+ }
81
+ }
82
+ /**
83
+ * Remove a contract from watching.
84
+ */
85
+ async removeContract(contractScript) {
86
+ const state = this.contracts.get(contractScript);
87
+ if (state) {
88
+ this.contracts.delete(contractScript);
89
+ if (this.isWatching) {
90
+ await this.tryUpdateSubscription();
91
+ }
92
+ }
93
+ }
94
+ /**
95
+ * Get all in-memory contracts.
96
+ */
97
+ getAllContracts() {
98
+ return Array.from(this.contracts.values()).map((s) => s.contract);
99
+ }
100
+ /**
101
+ * Get all active in-memory contracts.
102
+ */
103
+ getActiveContracts() {
104
+ return this.getAllContracts().filter((c) => c.state === "active");
105
+ }
106
+ /**
107
+ * Get scripts that should be watched.
108
+ *
109
+ * Returns scripts for:
110
+ * - All active contracts
111
+ * - All contracts with known VTXOs (regardless of state)
112
+ *
113
+ * This ensures we continue monitoring contracts even after they're
114
+ * deactivated, as long as they have unspent VTXOs.
115
+ */
116
+ getScriptsToWatch() {
117
+ const scripts = new Set();
118
+ for (const [, state] of this.contracts) {
119
+ // Always watch active contracts
120
+ if (state.contract.state === "active") {
121
+ scripts.add(state.contract.script);
122
+ continue;
123
+ }
124
+ // Also watch inactive/expired contracts that have VTXOs
125
+ if (state.lastKnownVtxos.size > 0) {
126
+ scripts.add(state.contract.script);
127
+ }
128
+ }
129
+ return Array.from(scripts);
130
+ }
131
+ /**
132
+ * Get VTXOs for contracts, grouped by contract script.
133
+ * Uses Repository.
134
+ */
135
+ async getContractVtxos(options) {
136
+ const { contractScripts, includeSpent } = options;
137
+ const repo = this.config.walletRepository;
138
+ const contractsToQuery = Array.from(this.contracts.values());
139
+ const asyncResults = contractsToQuery
140
+ .filter((_) => {
141
+ if (contractScripts &&
142
+ !contractScripts.includes(_.contract.script))
143
+ return false;
144
+ return true;
145
+ })
146
+ .map(async (state) => {
147
+ // Use contract address as cache key
148
+ const cached = await repo.getVtxos(state.contract.address);
149
+ if (cached.length > 0) {
150
+ // Convert to ContractVtxo with contractScript
151
+ const contractVtxos = cached.map((v) => ({
152
+ ...v,
153
+ contractScript: state.contract.script,
154
+ }));
155
+ const filtered = includeSpent
156
+ ? contractVtxos
157
+ : contractVtxos.filter((v) => !v.isSpent);
158
+ return [[state.contract.script, filtered]];
159
+ }
160
+ return [];
161
+ });
162
+ const results = await Promise.all(asyncResults);
163
+ return new Map(results.flat(1));
164
+ }
165
+ /**
166
+ * Start watching for VTXO events across all active contracts.
167
+ */
168
+ async startWatching(callback) {
169
+ if (this.isWatching) {
170
+ throw new Error("Already watching");
171
+ }
172
+ this.eventCallback = callback;
173
+ this.isWatching = true;
174
+ this.abortController = new AbortController();
175
+ this.reconnectAttempts = 0;
176
+ // Start connection
177
+ await this.connect();
178
+ // Start failsafe polling
179
+ this.startFailsafePolling();
180
+ return () => this.stopWatching();
181
+ }
182
+ /**
183
+ * Stop watching for events.
184
+ */
185
+ async stopWatching() {
186
+ this.isWatching = false;
187
+ this.connectionState = "disconnected";
188
+ this.abortController?.abort();
189
+ // Clear timers
190
+ if (this.reconnectTimeoutId) {
191
+ clearTimeout(this.reconnectTimeoutId);
192
+ this.reconnectTimeoutId = undefined;
193
+ }
194
+ if (this.failsafePollIntervalId) {
195
+ clearInterval(this.failsafePollIntervalId);
196
+ this.failsafePollIntervalId = undefined;
197
+ }
198
+ // Unsubscribe
199
+ if (this.subscriptionId) {
200
+ try {
201
+ await this.config.indexerProvider.unsubscribeForScripts(this.subscriptionId);
202
+ }
203
+ catch {
204
+ // Ignore unsubscribe errors
205
+ }
206
+ this.subscriptionId = undefined;
207
+ }
208
+ this.eventCallback = undefined;
209
+ }
210
+ /**
211
+ * Check if currently watching.
212
+ */
213
+ isCurrentlyWatching() {
214
+ return this.isWatching;
215
+ }
216
+ /**
217
+ * Get current connection state.
218
+ */
219
+ getConnectionState() {
220
+ return this.connectionState;
221
+ }
222
+ /**
223
+ * Force a poll of all active contracts.
224
+ * Useful for manual refresh or after app resume.
225
+ */
226
+ async forcePoll() {
227
+ if (!this.isWatching)
228
+ return;
229
+ await this.pollAllContracts();
230
+ }
231
+ /**
232
+ * Check for expired contracts, update their state, and emit events.
233
+ */
234
+ checkExpiredContracts() {
235
+ const now = Date.now();
236
+ const expired = [];
237
+ for (const state of this.contracts.values()) {
238
+ const contract = state.contract;
239
+ if (contract.state === "active" &&
240
+ contract.expiresAt &&
241
+ contract.expiresAt <= now) {
242
+ contract.state = "inactive";
243
+ expired.push(contract);
244
+ this.eventCallback?.({
245
+ type: "contract_expired",
246
+ contractScript: contract.script,
247
+ contract,
248
+ timestamp: now,
249
+ });
250
+ }
251
+ }
252
+ }
253
+ /**
254
+ * Connect to the subscription.
255
+ */
256
+ async connect() {
257
+ if (!this.isWatching)
258
+ return;
259
+ this.connectionState = "connecting";
260
+ try {
261
+ await this.updateSubscription();
262
+ // Poll immediately after connection to sync state
263
+ await this.pollAllContracts();
264
+ this.connectionState = "connected";
265
+ this.reconnectAttempts = 0;
266
+ // Start listening
267
+ this.listenLoop().catch((e) => {
268
+ // This is handled asynchronously otherwise `connect()` would hang
269
+ // indefinitely and block the caller.
270
+ // Error management must be implemented to ensure the connection
271
+ // is restored and events are fired.
272
+ console.error(e);
273
+ this.connectionState = "disconnected";
274
+ this.eventCallback?.({
275
+ type: "connection_reset",
276
+ timestamp: Date.now(),
277
+ });
278
+ this.scheduleReconnect();
279
+ });
280
+ }
281
+ catch (error) {
282
+ console.error("ContractWatcher connection failed:", error);
283
+ this.connectionState = "disconnected";
284
+ this.eventCallback?.({
285
+ type: "connection_reset",
286
+ timestamp: Date.now(),
287
+ });
288
+ this.scheduleReconnect();
289
+ }
290
+ }
291
+ /**
292
+ * Schedule a reconnection attempt.
293
+ */
294
+ scheduleReconnect() {
295
+ if (!this.isWatching)
296
+ return;
297
+ // Check max attempts
298
+ if (this.config.maxReconnectAttempts > 0 &&
299
+ this.reconnectAttempts >= this.config.maxReconnectAttempts) {
300
+ console.error(`ContractWatcher: Max reconnection attempts (${this.config.maxReconnectAttempts}) reached`);
301
+ return;
302
+ }
303
+ this.connectionState = "reconnecting";
304
+ this.reconnectAttempts++;
305
+ // Calculate delay with exponential backoff
306
+ const delay = Math.min(this.config.reconnectDelayMs *
307
+ Math.pow(2, this.reconnectAttempts - 1), this.config.maxReconnectDelayMs);
308
+ this.reconnectTimeoutId = setTimeout(() => {
309
+ this.reconnectTimeoutId = undefined;
310
+ this.connect();
311
+ }, delay);
312
+ }
313
+ /**
314
+ * Start the failsafe polling interval.
315
+ */
316
+ startFailsafePolling() {
317
+ if (this.failsafePollIntervalId) {
318
+ clearInterval(this.failsafePollIntervalId);
319
+ }
320
+ this.failsafePollIntervalId = setInterval(() => {
321
+ if (this.isWatching) {
322
+ this.pollAllContracts().catch((error) => {
323
+ console.error("ContractWatcher failsafe poll failed:", error);
324
+ });
325
+ }
326
+ }, this.config.failsafePollIntervalMs);
327
+ }
328
+ /**
329
+ * Poll all active contracts for current state.
330
+ */
331
+ async pollAllContracts() {
332
+ const activeScripts = this.getActiveContracts().map((c) => c.script);
333
+ if (activeScripts.length === 0)
334
+ return;
335
+ await this.pollContracts(activeScripts);
336
+ }
337
+ /**
338
+ * Poll specific contracts and emit events for changes.
339
+ */
340
+ async pollContracts(contractScripts) {
341
+ if (!this.eventCallback)
342
+ return;
343
+ const now = Date.now();
344
+ try {
345
+ // Load all the VTXOs for these contracts, from DB
346
+ const vtxosMap = await this.getContractVtxos({
347
+ contractScripts,
348
+ includeSpent: false, // only spendable ones!
349
+ });
350
+ for (const contractScript of contractScripts) {
351
+ const state = this.contracts.get(contractScript);
352
+ if (!state)
353
+ continue;
354
+ const currentVtxos = vtxosMap.get(contractScript) || [];
355
+ const currentKeys = new Set(currentVtxos.map((v) => `${v.txid}:${v.vout}`));
356
+ // Find new VTXOs and add them to the contract's state
357
+ const newVtxos = [];
358
+ for (const vtxo of currentVtxos) {
359
+ const key = `${vtxo.txid}:${vtxo.vout}`;
360
+ if (!state.lastKnownVtxos.has(key)) {
361
+ newVtxos.push(vtxo);
362
+ state.lastKnownVtxos.set(key, vtxo);
363
+ }
364
+ }
365
+ // Find spent VTXOs and remove them from the contract's state
366
+ const spentVtxos = [];
367
+ for (const [key, vtxo] of state.lastKnownVtxos) {
368
+ if (!currentKeys.has(key)) {
369
+ spentVtxos.push(vtxo);
370
+ state.lastKnownVtxos.delete(key);
371
+ }
372
+ }
373
+ // Emit events
374
+ if (newVtxos.length > 0) {
375
+ this.emitVtxoEvent(contractScript, newVtxos, "vtxo_received", now);
376
+ }
377
+ if (spentVtxos.length > 0) {
378
+ // Note: We can't distinguish spent vs swept from polling alone
379
+ // The subscription provides more accurate event types
380
+ this.emitVtxoEvent(contractScript, spentVtxos, "vtxo_spent", now);
381
+ }
382
+ }
383
+ }
384
+ catch (error) {
385
+ console.error("ContractWatcher poll failed:", error);
386
+ // Don't throw - polling failures shouldn't crash the watcher
387
+ }
388
+ }
389
+ async tryUpdateSubscription() {
390
+ try {
391
+ await this.updateSubscription();
392
+ }
393
+ catch (error) {
394
+ // nothing, the connection will be retried later
395
+ }
396
+ }
397
+ /**
398
+ * Update the subscription with scripts that should be watched.
399
+ *
400
+ * Watches both active contracts and contracts with VTXOs.
401
+ */
402
+ async updateSubscription() {
403
+ const scriptsToWatch = this.getScriptsToWatch();
404
+ if (scriptsToWatch.length === 0) {
405
+ if (this.subscriptionId) {
406
+ try {
407
+ await this.config.indexerProvider.unsubscribeForScripts(this.subscriptionId);
408
+ }
409
+ catch {
410
+ // Ignore
411
+ }
412
+ this.subscriptionId = undefined;
413
+ }
414
+ return;
415
+ }
416
+ this.subscriptionId =
417
+ await this.config.indexerProvider.subscribeForScripts(scriptsToWatch, this.subscriptionId);
418
+ }
419
+ /**
420
+ * Main listening loop for subscription events.
421
+ */
422
+ async listenLoop() {
423
+ if (!this.subscriptionId || !this.abortController || !this.isWatching) {
424
+ if (this.isWatching) {
425
+ this.connectionState = "disconnected";
426
+ this.scheduleReconnect();
427
+ }
428
+ return;
429
+ }
430
+ const subscription = this.config.indexerProvider.getSubscription(this.subscriptionId, this.abortController.signal);
431
+ for await (const update of subscription) {
432
+ if (!this.isWatching)
433
+ break;
434
+ this.handleSubscriptionUpdate(update);
435
+ }
436
+ // Stream ended normally - reconnect if still watching
437
+ if (this.isWatching) {
438
+ this.connectionState = "disconnected";
439
+ this.scheduleReconnect();
440
+ }
441
+ }
442
+ /**
443
+ * Handle a subscription update.
444
+ */
445
+ handleSubscriptionUpdate(update) {
446
+ if (!this.eventCallback)
447
+ return;
448
+ const timestamp = Date.now();
449
+ const scripts = update.scripts || [];
450
+ if (update.newVtxos?.length) {
451
+ this.processSubscriptionVtxos(update.newVtxos, scripts, "vtxo_received", timestamp);
452
+ }
453
+ if (update.spentVtxos?.length) {
454
+ this.processSubscriptionVtxos(update.spentVtxos, scripts, "vtxo_spent", timestamp);
455
+ }
456
+ }
457
+ /**
458
+ * Process VTXOs from subscription and route to correct contracts.
459
+ * Uses the scripts from the subscription response to determine contract ownership.
460
+ */
461
+ processSubscriptionVtxos(vtxos, scripts, eventType, timestamp) {
462
+ // If we have exactly one script, all VTXOs belong to that contract
463
+ // Otherwise, we can't reliably determine ownership without script in VirtualCoin
464
+ if (scripts.length === 1) {
465
+ const contractScript = scripts[0];
466
+ if (contractScript) {
467
+ // Update tracking
468
+ const state = this.contracts.get(contractScript);
469
+ if (state) {
470
+ for (const vtxo of vtxos) {
471
+ const key = `${vtxo.txid}:${vtxo.vout}`;
472
+ if (eventType === "vtxo_received") {
473
+ state.lastKnownVtxos.set(key, vtxo);
474
+ }
475
+ else if (eventType === "vtxo_spent") {
476
+ state.lastKnownVtxos.delete(key);
477
+ }
478
+ }
479
+ }
480
+ this.emitVtxoEvent(contractScript, vtxos, eventType, timestamp);
481
+ }
482
+ return;
483
+ }
484
+ // Multiple scripts - assign VTXOs to all matching contracts
485
+ // This is a limitation: we can't know which VTXO belongs to which script
486
+ // In practice, subscription events usually come with a single script context
487
+ for (const script of scripts) {
488
+ const contractScript = script;
489
+ if (contractScript) {
490
+ const state = this.contracts.get(contractScript);
491
+ if (state) {
492
+ for (const vtxo of vtxos) {
493
+ const key = `${vtxo.txid}:${vtxo.vout}`;
494
+ if (eventType === "vtxo_received") {
495
+ state.lastKnownVtxos.set(key, vtxo);
496
+ }
497
+ else {
498
+ state.lastKnownVtxos.delete(key);
499
+ }
500
+ }
501
+ }
502
+ this.emitVtxoEvent(contractScript, vtxos, eventType, timestamp);
503
+ }
504
+ }
505
+ }
506
+ /**
507
+ * Emit a VTXO event for a contract.
508
+ */
509
+ emitVtxoEvent(contractScript, vtxos, eventType, timestamp) {
510
+ if (!this.eventCallback)
511
+ return;
512
+ const state = this.contracts.get(contractScript);
513
+ // ensure we check somehow regularly
514
+ this.checkExpiredContracts();
515
+ switch (eventType) {
516
+ case "vtxo_received":
517
+ if (!state)
518
+ return;
519
+ this.eventCallback({
520
+ type: "vtxo_received",
521
+ vtxos: vtxos.map((v) => ({
522
+ ...v,
523
+ contractScript,
524
+ // These fields may not be available from basic VirtualCoin
525
+ forfeitTapLeafScript: undefined,
526
+ intentTapLeafScript: undefined,
527
+ tapTree: undefined,
528
+ })),
529
+ contractScript,
530
+ contract: state.contract,
531
+ timestamp,
532
+ });
533
+ return;
534
+ case "vtxo_spent":
535
+ if (!state)
536
+ return;
537
+ this.eventCallback({
538
+ type: "vtxo_spent",
539
+ vtxos: vtxos.map((v) => ({
540
+ ...v,
541
+ contractScript,
542
+ // These fields may not be available from basic VirtualCoin
543
+ forfeitTapLeafScript: undefined,
544
+ intentTapLeafScript: undefined,
545
+ tapTree: undefined,
546
+ })),
547
+ contractScript,
548
+ contract: state.contract,
549
+ timestamp,
550
+ });
551
+ return;
552
+ case "contract_expired":
553
+ if (!state)
554
+ return;
555
+ this.eventCallback({
556
+ type: "contract_expired",
557
+ contractScript,
558
+ contract: state.contract,
559
+ timestamp,
560
+ });
561
+ return;
562
+ default:
563
+ return;
564
+ }
565
+ }
566
+ }
567
+ exports.ContractWatcher = ContractWatcher;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefaultContractHandler = void 0;
4
+ const base_1 = require("@scure/base");
5
+ const default_1 = require("../../script/default");
6
+ const helpers_1 = require("./helpers");
7
+ /**
8
+ * Handler for default wallet VTXOs.
9
+ *
10
+ * Default contracts use the standard forfeit + exit tapscript:
11
+ * - forfeit: (Alice + Server) multisig for collaborative spending
12
+ * - exit: (Alice) + CSV timelock for unilateral exit
13
+ */
14
+ exports.DefaultContractHandler = {
15
+ type: "default",
16
+ createScript(params) {
17
+ const typed = this.deserializeParams(params);
18
+ return new default_1.DefaultVtxo.Script(typed);
19
+ },
20
+ serializeParams(params) {
21
+ return {
22
+ pubKey: base_1.hex.encode(params.pubKey),
23
+ serverPubKey: base_1.hex.encode(params.serverPubKey),
24
+ csvTimelock: (0, helpers_1.timelockToSequence)(params.csvTimelock).toString(),
25
+ };
26
+ },
27
+ deserializeParams(params) {
28
+ const csvTimelock = params.csvTimelock
29
+ ? (0, helpers_1.sequenceToTimelock)(Number(params.csvTimelock))
30
+ : default_1.DefaultVtxo.Script.DEFAULT_TIMELOCK;
31
+ return {
32
+ pubKey: base_1.hex.decode(params.pubKey),
33
+ serverPubKey: base_1.hex.decode(params.serverPubKey),
34
+ csvTimelock,
35
+ };
36
+ },
37
+ selectPath(script, contract, context) {
38
+ if (context.collaborative) {
39
+ // Use forfeit path for collaborative spending
40
+ return { leaf: script.forfeit() };
41
+ }
42
+ // Use exit path for unilateral exit (only if CSV is satisfied)
43
+ const sequence = contract.params.csvTimelock
44
+ ? Number(contract.params.csvTimelock)
45
+ : undefined;
46
+ if (!(0, helpers_1.isCsvSpendable)(context, sequence)) {
47
+ return null;
48
+ }
49
+ return {
50
+ leaf: script.exit(),
51
+ sequence,
52
+ };
53
+ },
54
+ getAllSpendingPaths(script, contract, context) {
55
+ const paths = [];
56
+ // Forfeit path available with server cooperation
57
+ if (context.collaborative) {
58
+ paths.push({ leaf: script.forfeit() });
59
+ }
60
+ // Exit path always possible (CSV checked at tx time)
61
+ const exitPath = { leaf: script.exit() };
62
+ if (contract.params.csvTimelock) {
63
+ exitPath.sequence = Number(contract.params.csvTimelock);
64
+ }
65
+ paths.push(exitPath);
66
+ return paths;
67
+ },
68
+ getSpendablePaths(script, contract, context) {
69
+ const paths = [];
70
+ if (context.collaborative) {
71
+ paths.push({ leaf: script.forfeit() });
72
+ }
73
+ const exitSequence = contract.params.csvTimelock
74
+ ? Number(contract.params.csvTimelock)
75
+ : undefined;
76
+ if ((0, helpers_1.isCsvSpendable)(context, exitSequence)) {
77
+ const exitPath = { leaf: script.exit() };
78
+ if (exitSequence !== undefined) {
79
+ exitPath.sequence = exitSequence;
80
+ }
81
+ paths.push(exitPath);
82
+ }
83
+ return paths;
84
+ },
85
+ };