@arkade-os/sdk 0.4.27 → 0.4.29

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 (532) hide show
  1. package/README.md +45 -116
  2. package/dist/adapters/asyncStorage.cjs +48 -0
  3. package/dist/adapters/asyncStorage.cjs.map +1 -0
  4. package/dist/adapters/asyncStorage.d.cts +16 -0
  5. package/dist/{types/storage → adapters}/asyncStorage.d.ts +5 -2
  6. package/dist/adapters/asyncStorage.js +46 -0
  7. package/dist/adapters/asyncStorage.js.map +1 -0
  8. package/dist/adapters/expo.cjs +19 -0
  9. package/dist/adapters/expo.cjs.map +1 -0
  10. package/dist/adapters/expo.d.cts +48 -0
  11. package/dist/adapters/expo.d.ts +48 -0
  12. package/dist/adapters/expo.js +6 -0
  13. package/dist/adapters/expo.js.map +1 -0
  14. package/dist/adapters/fileSystem.cjs +116 -0
  15. package/dist/adapters/fileSystem.cjs.map +1 -0
  16. package/dist/adapters/fileSystem.d.cts +17 -0
  17. package/dist/{types/storage → adapters}/fileSystem.d.ts +5 -2
  18. package/dist/adapters/fileSystem.js +93 -0
  19. package/dist/adapters/fileSystem.js.map +1 -0
  20. package/dist/adapters/indexedDB.cjs +103 -0
  21. package/dist/adapters/indexedDB.cjs.map +1 -0
  22. package/dist/adapters/indexedDB.d.cts +18 -0
  23. package/dist/{types/storage → adapters}/indexedDB.d.ts +5 -2
  24. package/dist/adapters/indexedDB.js +101 -0
  25. package/dist/adapters/indexedDB.js.map +1 -0
  26. package/dist/adapters/localStorage.cjs +50 -0
  27. package/dist/adapters/localStorage.cjs.map +1 -0
  28. package/dist/{types/storage/inMemory.d.ts → adapters/localStorage.d.cts} +6 -3
  29. package/dist/{types/storage → adapters}/localStorage.d.ts +5 -2
  30. package/dist/adapters/localStorage.js +48 -0
  31. package/dist/adapters/localStorage.js.map +1 -0
  32. package/dist/ark-ChhTwpLf.d.cts +3892 -0
  33. package/dist/ark-ChhTwpLf.d.ts +3892 -0
  34. package/dist/asyncStorageTaskQueue-DW1-BpI7.d.cts +49 -0
  35. package/dist/{types/worker/expo/asyncStorageTaskQueue.d.ts → asyncStorageTaskQueue-DZ0nUuEJ.d.ts} +6 -3
  36. package/dist/chunk-5BLDMQED.cjs +18 -0
  37. package/dist/chunk-5BLDMQED.cjs.map +1 -0
  38. package/dist/chunk-6FLL2Q36.cjs +2701 -0
  39. package/dist/chunk-6FLL2Q36.cjs.map +1 -0
  40. package/dist/chunk-6NWNOLL3.js +2671 -0
  41. package/dist/chunk-6NWNOLL3.js.map +1 -0
  42. package/dist/chunk-ABWRLTX5.js +210 -0
  43. package/dist/chunk-ABWRLTX5.js.map +1 -0
  44. package/dist/chunk-BVP2U66Q.js +13943 -0
  45. package/dist/chunk-BVP2U66Q.js.map +1 -0
  46. package/dist/chunk-GDCTOSMV.cjs +14058 -0
  47. package/dist/chunk-GDCTOSMV.cjs.map +1 -0
  48. package/dist/chunk-GIGILVVP.cjs +213 -0
  49. package/dist/chunk-GIGILVVP.cjs.map +1 -0
  50. package/dist/chunk-IEO3XDKI.cjs +838 -0
  51. package/dist/chunk-IEO3XDKI.cjs.map +1 -0
  52. package/dist/chunk-NSBPE2FW.js +15 -0
  53. package/dist/chunk-NSBPE2FW.js.map +1 -0
  54. package/dist/chunk-PJUFOJ2L.cjs +100 -0
  55. package/dist/chunk-PJUFOJ2L.cjs.map +1 -0
  56. package/dist/chunk-TH6T23XG.js +95 -0
  57. package/dist/chunk-TH6T23XG.js.map +1 -0
  58. package/dist/chunk-TU3LVAPX.js +769 -0
  59. package/dist/chunk-TU3LVAPX.js.map +1 -0
  60. package/dist/chunk-WMIPYZSB.cjs +803 -0
  61. package/dist/chunk-WMIPYZSB.cjs.map +1 -0
  62. package/dist/chunk-YA4G7RFB.js +829 -0
  63. package/dist/chunk-YA4G7RFB.js.map +1 -0
  64. package/dist/contracts/handlers/index.cjs +26 -0
  65. package/dist/contracts/handlers/index.cjs.map +1 -0
  66. package/dist/contracts/handlers/index.d.cts +7 -0
  67. package/dist/contracts/handlers/index.d.ts +7 -0
  68. package/dist/contracts/handlers/index.js +5 -0
  69. package/dist/contracts/handlers/index.js.map +1 -0
  70. package/dist/delegate-4JBUkUhR.d.cts +84 -0
  71. package/dist/delegate-DN7RELL1.d.ts +84 -0
  72. package/dist/{types/storage/index.d.ts → index-C0IanN1m.d.cts} +3 -1
  73. package/dist/index-C0IanN1m.d.ts +11 -0
  74. package/dist/index-Cn82bBUu.d.ts +199 -0
  75. package/dist/index-DfT5xzgY.d.cts +199 -0
  76. package/dist/index.cjs +504 -0
  77. package/dist/index.cjs.map +1 -0
  78. package/dist/index.d.cts +3469 -0
  79. package/dist/index.d.ts +3469 -0
  80. package/dist/index.js +7 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/repositories/realm/index.cjs +513 -0
  83. package/dist/repositories/realm/index.cjs.map +1 -0
  84. package/dist/repositories/realm/index.d.cts +217 -0
  85. package/dist/{types/repositories/realm/schemas.d.ts → repositories/realm/index.d.ts} +80 -112
  86. package/dist/repositories/realm/index.js +507 -0
  87. package/dist/repositories/realm/index.js.map +1 -0
  88. package/dist/repositories/sqlite/index.cjs +588 -0
  89. package/dist/repositories/sqlite/index.cjs.map +1 -0
  90. package/dist/repositories/sqlite/index.d.cts +118 -0
  91. package/dist/{types/repositories/sqlite/walletRepository.d.ts → repositories/sqlite/index.d.ts} +58 -5
  92. package/dist/repositories/sqlite/index.js +585 -0
  93. package/dist/repositories/sqlite/index.js.map +1 -0
  94. package/dist/taskRunner-B-aPfHhK.d.cts +114 -0
  95. package/dist/taskRunner-B-vG08pX.d.ts +114 -0
  96. package/dist/wallet/expo/background.cjs +93 -0
  97. package/dist/wallet/expo/background.cjs.map +1 -0
  98. package/dist/wallet/expo/background.d.cts +84 -0
  99. package/dist/wallet/expo/background.d.ts +84 -0
  100. package/dist/wallet/expo/background.js +68 -0
  101. package/dist/wallet/expo/background.js.map +1 -0
  102. package/dist/wallet/expo/index.cjs +175 -0
  103. package/dist/wallet/expo/index.cjs.map +1 -0
  104. package/dist/wallet/expo/index.d.cts +124 -0
  105. package/dist/wallet/expo/index.d.ts +124 -0
  106. package/dist/wallet/expo/index.js +173 -0
  107. package/dist/wallet/expo/index.js.map +1 -0
  108. package/dist/wallet-CCtqT2Wb.d.ts +778 -0
  109. package/dist/wallet-DjgFb_4T.d.cts +778 -0
  110. package/dist/worker/expo/index.cjs +140 -0
  111. package/dist/worker/expo/index.cjs.map +1 -0
  112. package/dist/worker/expo/index.d.cts +29 -0
  113. package/dist/worker/expo/index.d.ts +29 -0
  114. package/dist/worker/expo/index.js +121 -0
  115. package/dist/worker/expo/index.js.map +1 -0
  116. package/package.json +110 -76
  117. package/dist/cjs/adapters/asyncStorage.js +0 -5
  118. package/dist/cjs/adapters/expo.js +0 -8
  119. package/dist/cjs/adapters/fileSystem.js +0 -5
  120. package/dist/cjs/adapters/indexedDB.js +0 -5
  121. package/dist/cjs/adapters/localStorage.js +0 -5
  122. package/dist/cjs/arkfee/celenv.js +0 -43
  123. package/dist/cjs/arkfee/estimator.js +0 -143
  124. package/dist/cjs/arkfee/index.js +0 -5
  125. package/dist/cjs/arkfee/types.js +0 -26
  126. package/dist/cjs/arknote/index.js +0 -128
  127. package/dist/cjs/bip322/index.js +0 -270
  128. package/dist/cjs/contracts/arkcontract.js +0 -147
  129. package/dist/cjs/contracts/contractManager.js +0 -649
  130. package/dist/cjs/contracts/contractWatcher.js +0 -598
  131. package/dist/cjs/contracts/handlers/default.js +0 -93
  132. package/dist/cjs/contracts/handlers/delegate.js +0 -90
  133. package/dist/cjs/contracts/handlers/helpers.js +0 -115
  134. package/dist/cjs/contracts/handlers/index.js +0 -19
  135. package/dist/cjs/contracts/handlers/registry.js +0 -89
  136. package/dist/cjs/contracts/handlers/vhtlc.js +0 -194
  137. package/dist/cjs/contracts/index.js +0 -41
  138. package/dist/cjs/contracts/types.js +0 -2
  139. package/dist/cjs/contracts/vtxoOwnership.js +0 -78
  140. package/dist/cjs/extension/asset/assetGroup.js +0 -228
  141. package/dist/cjs/extension/asset/assetId.js +0 -152
  142. package/dist/cjs/extension/asset/assetInput.js +0 -222
  143. package/dist/cjs/extension/asset/assetOutput.js +0 -174
  144. package/dist/cjs/extension/asset/assetRef.js +0 -148
  145. package/dist/cjs/extension/asset/index.js +0 -23
  146. package/dist/cjs/extension/asset/metadata.js +0 -187
  147. package/dist/cjs/extension/asset/packet.js +0 -114
  148. package/dist/cjs/extension/asset/types.js +0 -22
  149. package/dist/cjs/extension/asset/utils.js +0 -105
  150. package/dist/cjs/extension/index.js +0 -254
  151. package/dist/cjs/extension/packet.js +0 -20
  152. package/dist/cjs/forfeit.js +0 -45
  153. package/dist/cjs/identity/descriptor.js +0 -169
  154. package/dist/cjs/identity/descriptorProvider.js +0 -2
  155. package/dist/cjs/identity/hdCapableIdentity.js +0 -20
  156. package/dist/cjs/identity/index.js +0 -40
  157. package/dist/cjs/identity/seedIdentity.js +0 -477
  158. package/dist/cjs/identity/serialize.js +0 -171
  159. package/dist/cjs/identity/singleKey.js +0 -126
  160. package/dist/cjs/identity/staticDescriptorProvider.js +0 -65
  161. package/dist/cjs/index.js +0 -202
  162. package/dist/cjs/intent/index.js +0 -259
  163. package/dist/cjs/musig2/index.js +0 -11
  164. package/dist/cjs/musig2/keys.js +0 -57
  165. package/dist/cjs/musig2/nonces.js +0 -48
  166. package/dist/cjs/musig2/sign.js +0 -102
  167. package/dist/cjs/networks.js +0 -26
  168. package/dist/cjs/package.json +0 -3
  169. package/dist/cjs/providers/ark.js +0 -577
  170. package/dist/cjs/providers/delegator.js +0 -85
  171. package/dist/cjs/providers/electrum.js +0 -869
  172. package/dist/cjs/providers/errors.js +0 -59
  173. package/dist/cjs/providers/expoArk.js +0 -82
  174. package/dist/cjs/providers/expoIndexer.js +0 -111
  175. package/dist/cjs/providers/expoUtils.js +0 -124
  176. package/dist/cjs/providers/indexer.js +0 -630
  177. package/dist/cjs/providers/onchain.js +0 -262
  178. package/dist/cjs/providers/utils.js +0 -121
  179. package/dist/cjs/repositories/contractRepository.js +0 -2
  180. package/dist/cjs/repositories/inMemory/contractRepository.js +0 -55
  181. package/dist/cjs/repositories/inMemory/walletRepository.js +0 -115
  182. package/dist/cjs/repositories/index.js +0 -34
  183. package/dist/cjs/repositories/indexedDB/contractRepository.js +0 -187
  184. package/dist/cjs/repositories/indexedDB/db.js +0 -19
  185. package/dist/cjs/repositories/indexedDB/manager.js +0 -100
  186. package/dist/cjs/repositories/indexedDB/schema.js +0 -204
  187. package/dist/cjs/repositories/indexedDB/walletRepository.js +0 -474
  188. package/dist/cjs/repositories/indexedDB/websqlAdapter.js +0 -144
  189. package/dist/cjs/repositories/migrations/contractRepositoryImpl.js +0 -127
  190. package/dist/cjs/repositories/migrations/fromStorageAdapter.js +0 -66
  191. package/dist/cjs/repositories/migrations/walletRepositoryImpl.js +0 -184
  192. package/dist/cjs/repositories/realm/contractRepository.js +0 -116
  193. package/dist/cjs/repositories/realm/index.js +0 -11
  194. package/dist/cjs/repositories/realm/schemas.js +0 -157
  195. package/dist/cjs/repositories/realm/types.js +0 -7
  196. package/dist/cjs/repositories/realm/walletRepository.js +0 -305
  197. package/dist/cjs/repositories/scriptFromAddress.js +0 -16
  198. package/dist/cjs/repositories/serialization.js +0 -82
  199. package/dist/cjs/repositories/sqlite/contractRepository.js +0 -135
  200. package/dist/cjs/repositories/sqlite/index.js +0 -7
  201. package/dist/cjs/repositories/sqlite/types.js +0 -2
  202. package/dist/cjs/repositories/sqlite/walletRepository.js +0 -441
  203. package/dist/cjs/repositories/walletRepository.js +0 -2
  204. package/dist/cjs/script/address.js +0 -108
  205. package/dist/cjs/script/base.js +0 -185
  206. package/dist/cjs/script/default.js +0 -57
  207. package/dist/cjs/script/delegate.js +0 -53
  208. package/dist/cjs/script/tapscript.js +0 -619
  209. package/dist/cjs/script/vhtlc.js +0 -170
  210. package/dist/cjs/storage/asyncStorage.js +0 -50
  211. package/dist/cjs/storage/fileSystem.js +0 -141
  212. package/dist/cjs/storage/inMemory.js +0 -24
  213. package/dist/cjs/storage/index.js +0 -2
  214. package/dist/cjs/storage/indexedDB.js +0 -101
  215. package/dist/cjs/storage/localStorage.js +0 -51
  216. package/dist/cjs/tree/signingSession.js +0 -229
  217. package/dist/cjs/tree/txTree.js +0 -192
  218. package/dist/cjs/tree/validation.js +0 -107
  219. package/dist/cjs/utils/anchor.js +0 -35
  220. package/dist/cjs/utils/arkTransaction.js +0 -271
  221. package/dist/cjs/utils/bip21.js +0 -127
  222. package/dist/cjs/utils/syncCursors.js +0 -128
  223. package/dist/cjs/utils/timelock.js +0 -59
  224. package/dist/cjs/utils/transaction.js +0 -28
  225. package/dist/cjs/utils/transactionHistory.js +0 -183
  226. package/dist/cjs/utils/txSizeEstimator.js +0 -132
  227. package/dist/cjs/utils/unknownFields.js +0 -174
  228. package/dist/cjs/wallet/asset-manager.js +0 -330
  229. package/dist/cjs/wallet/asset.js +0 -119
  230. package/dist/cjs/wallet/batch.js +0 -183
  231. package/dist/cjs/wallet/delegator.js +0 -308
  232. package/dist/cjs/wallet/expo/background.js +0 -116
  233. package/dist/cjs/wallet/expo/index.js +0 -9
  234. package/dist/cjs/wallet/expo/wallet.js +0 -230
  235. package/dist/cjs/wallet/hdDescriptorProvider.js +0 -188
  236. package/dist/cjs/wallet/index.js +0 -82
  237. package/dist/cjs/wallet/inputSignerRouter.js +0 -98
  238. package/dist/cjs/wallet/onchain.js +0 -290
  239. package/dist/cjs/wallet/ramps.js +0 -216
  240. package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +0 -953
  241. package/dist/cjs/wallet/serviceWorker/wallet.js +0 -1174
  242. package/dist/cjs/wallet/signingErrors.js +0 -32
  243. package/dist/cjs/wallet/unroll.js +0 -293
  244. package/dist/cjs/wallet/utils.js +0 -111
  245. package/dist/cjs/wallet/validation.js +0 -154
  246. package/dist/cjs/wallet/vtxo-manager.js +0 -1142
  247. package/dist/cjs/wallet/wallet.js +0 -2195
  248. package/dist/cjs/wallet/walletReceiveRotator.js +0 -547
  249. package/dist/cjs/worker/browser/service-worker-manager.js +0 -183
  250. package/dist/cjs/worker/browser/utils.js +0 -67
  251. package/dist/cjs/worker/errors.js +0 -16
  252. package/dist/cjs/worker/expo/asyncStorageTaskQueue.js +0 -78
  253. package/dist/cjs/worker/expo/index.js +0 -13
  254. package/dist/cjs/worker/expo/processors/contractPollProcessor.js +0 -62
  255. package/dist/cjs/worker/expo/processors/index.js +0 -6
  256. package/dist/cjs/worker/expo/taskQueue.js +0 -41
  257. package/dist/cjs/worker/expo/taskRunner.js +0 -73
  258. package/dist/cjs/worker/messageBus.js +0 -474
  259. package/dist/esm/adapters/asyncStorage.js +0 -1
  260. package/dist/esm/adapters/expo.js +0 -3
  261. package/dist/esm/adapters/fileSystem.js +0 -1
  262. package/dist/esm/adapters/indexedDB.js +0 -1
  263. package/dist/esm/adapters/localStorage.js +0 -1
  264. package/dist/esm/arkfee/celenv.js +0 -40
  265. package/dist/esm/arkfee/estimator.js +0 -139
  266. package/dist/esm/arkfee/index.js +0 -1
  267. package/dist/esm/arkfee/types.js +0 -22
  268. package/dist/esm/arknote/index.js +0 -124
  269. package/dist/esm/bip322/index.js +0 -267
  270. package/dist/esm/contracts/arkcontract.js +0 -140
  271. package/dist/esm/contracts/contractManager.js +0 -645
  272. package/dist/esm/contracts/contractWatcher.js +0 -594
  273. package/dist/esm/contracts/handlers/default.js +0 -90
  274. package/dist/esm/contracts/handlers/delegate.js +0 -87
  275. package/dist/esm/contracts/handlers/helpers.js +0 -110
  276. package/dist/esm/contracts/handlers/index.js +0 -12
  277. package/dist/esm/contracts/handlers/registry.js +0 -86
  278. package/dist/esm/contracts/handlers/vhtlc.js +0 -191
  279. package/dist/esm/contracts/index.js +0 -13
  280. package/dist/esm/contracts/types.js +0 -1
  281. package/dist/esm/contracts/vtxoOwnership.js +0 -69
  282. package/dist/esm/extension/asset/assetGroup.js +0 -224
  283. package/dist/esm/extension/asset/assetId.js +0 -148
  284. package/dist/esm/extension/asset/assetInput.js +0 -217
  285. package/dist/esm/extension/asset/assetOutput.js +0 -169
  286. package/dist/esm/extension/asset/assetRef.js +0 -144
  287. package/dist/esm/extension/asset/index.js +0 -8
  288. package/dist/esm/extension/asset/metadata.js +0 -182
  289. package/dist/esm/extension/asset/packet.js +0 -110
  290. package/dist/esm/extension/asset/types.js +0 -19
  291. package/dist/esm/extension/asset/utils.js +0 -99
  292. package/dist/esm/extension/index.js +0 -248
  293. package/dist/esm/extension/packet.js +0 -16
  294. package/dist/esm/forfeit.js +0 -41
  295. package/dist/esm/identity/descriptor.js +0 -161
  296. package/dist/esm/identity/descriptorProvider.js +0 -1
  297. package/dist/esm/identity/hdCapableIdentity.js +0 -17
  298. package/dist/esm/identity/index.js +0 -13
  299. package/dist/esm/identity/seedIdentity.js +0 -469
  300. package/dist/esm/identity/serialize.js +0 -164
  301. package/dist/esm/identity/singleKey.js +0 -121
  302. package/dist/esm/identity/staticDescriptorProvider.js +0 -61
  303. package/dist/esm/index.js +0 -87
  304. package/dist/esm/intent/index.js +0 -255
  305. package/dist/esm/musig2/index.js +0 -3
  306. package/dist/esm/musig2/keys.js +0 -21
  307. package/dist/esm/musig2/nonces.js +0 -11
  308. package/dist/esm/musig2/sign.js +0 -63
  309. package/dist/esm/networks.js +0 -22
  310. package/dist/esm/package.json +0 -3
  311. package/dist/esm/providers/ark.js +0 -572
  312. package/dist/esm/providers/delegator.js +0 -81
  313. package/dist/esm/providers/electrum.js +0 -864
  314. package/dist/esm/providers/errors.js +0 -54
  315. package/dist/esm/providers/expoArk.js +0 -78
  316. package/dist/esm/providers/expoIndexer.js +0 -107
  317. package/dist/esm/providers/expoUtils.js +0 -87
  318. package/dist/esm/providers/indexer.js +0 -626
  319. package/dist/esm/providers/onchain.js +0 -258
  320. package/dist/esm/providers/utils.js +0 -117
  321. package/dist/esm/repositories/contractRepository.js +0 -1
  322. package/dist/esm/repositories/inMemory/contractRepository.js +0 -51
  323. package/dist/esm/repositories/inMemory/walletRepository.js +0 -111
  324. package/dist/esm/repositories/index.js +0 -10
  325. package/dist/esm/repositories/indexedDB/contractRepository.js +0 -183
  326. package/dist/esm/repositories/indexedDB/db.js +0 -4
  327. package/dist/esm/repositories/indexedDB/manager.js +0 -95
  328. package/dist/esm/repositories/indexedDB/schema.js +0 -199
  329. package/dist/esm/repositories/indexedDB/walletRepository.js +0 -470
  330. package/dist/esm/repositories/indexedDB/websqlAdapter.js +0 -138
  331. package/dist/esm/repositories/migrations/contractRepositoryImpl.js +0 -121
  332. package/dist/esm/repositories/migrations/fromStorageAdapter.js +0 -58
  333. package/dist/esm/repositories/migrations/walletRepositoryImpl.js +0 -180
  334. package/dist/esm/repositories/realm/contractRepository.js +0 -112
  335. package/dist/esm/repositories/realm/index.js +0 -3
  336. package/dist/esm/repositories/realm/schemas.js +0 -153
  337. package/dist/esm/repositories/realm/types.js +0 -6
  338. package/dist/esm/repositories/realm/walletRepository.js +0 -301
  339. package/dist/esm/repositories/scriptFromAddress.js +0 -13
  340. package/dist/esm/repositories/serialization.js +0 -67
  341. package/dist/esm/repositories/sqlite/contractRepository.js +0 -131
  342. package/dist/esm/repositories/sqlite/index.js +0 -2
  343. package/dist/esm/repositories/sqlite/types.js +0 -1
  344. package/dist/esm/repositories/sqlite/walletRepository.js +0 -437
  345. package/dist/esm/repositories/walletRepository.js +0 -1
  346. package/dist/esm/script/address.js +0 -104
  347. package/dist/esm/script/base.js +0 -179
  348. package/dist/esm/script/default.js +0 -54
  349. package/dist/esm/script/delegate.js +0 -50
  350. package/dist/esm/script/tapscript.js +0 -615
  351. package/dist/esm/script/vhtlc.js +0 -167
  352. package/dist/esm/storage/asyncStorage.js +0 -46
  353. package/dist/esm/storage/fileSystem.js +0 -104
  354. package/dist/esm/storage/inMemory.js +0 -20
  355. package/dist/esm/storage/index.js +0 -1
  356. package/dist/esm/storage/indexedDB.js +0 -97
  357. package/dist/esm/storage/localStorage.js +0 -47
  358. package/dist/esm/tree/signingSession.js +0 -191
  359. package/dist/esm/tree/txTree.js +0 -188
  360. package/dist/esm/tree/validation.js +0 -101
  361. package/dist/esm/utils/anchor.js +0 -31
  362. package/dist/esm/utils/arkTransaction.js +0 -264
  363. package/dist/esm/utils/bip21.js +0 -123
  364. package/dist/esm/utils/syncCursors.js +0 -119
  365. package/dist/esm/utils/timelock.js +0 -22
  366. package/dist/esm/utils/transaction.js +0 -24
  367. package/dist/esm/utils/transactionHistory.js +0 -180
  368. package/dist/esm/utils/txSizeEstimator.js +0 -128
  369. package/dist/esm/utils/unknownFields.js +0 -169
  370. package/dist/esm/wallet/asset-manager.js +0 -325
  371. package/dist/esm/wallet/asset.js +0 -113
  372. package/dist/esm/wallet/batch.js +0 -180
  373. package/dist/esm/wallet/delegator.js +0 -303
  374. package/dist/esm/wallet/expo/background.js +0 -111
  375. package/dist/esm/wallet/expo/index.js +0 -2
  376. package/dist/esm/wallet/expo/wallet.js +0 -193
  377. package/dist/esm/wallet/hdDescriptorProvider.js +0 -184
  378. package/dist/esm/wallet/index.js +0 -75
  379. package/dist/esm/wallet/inputSignerRouter.js +0 -94
  380. package/dist/esm/wallet/onchain.js +0 -285
  381. package/dist/esm/wallet/ramps.js +0 -212
  382. package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +0 -946
  383. package/dist/esm/wallet/serviceWorker/wallet.js +0 -1169
  384. package/dist/esm/wallet/signingErrors.js +0 -27
  385. package/dist/esm/wallet/unroll.js +0 -289
  386. package/dist/esm/wallet/utils.js +0 -103
  387. package/dist/esm/wallet/validation.js +0 -142
  388. package/dist/esm/wallet/vtxo-manager.js +0 -1136
  389. package/dist/esm/wallet/wallet.js +0 -2186
  390. package/dist/esm/wallet/walletReceiveRotator.js +0 -540
  391. package/dist/esm/worker/browser/service-worker-manager.js +0 -177
  392. package/dist/esm/worker/browser/utils.js +0 -63
  393. package/dist/esm/worker/errors.js +0 -11
  394. package/dist/esm/worker/expo/asyncStorageTaskQueue.js +0 -74
  395. package/dist/esm/worker/expo/index.js +0 -4
  396. package/dist/esm/worker/expo/processors/contractPollProcessor.js +0 -59
  397. package/dist/esm/worker/expo/processors/index.js +0 -1
  398. package/dist/esm/worker/expo/taskQueue.js +0 -37
  399. package/dist/esm/worker/expo/taskRunner.js +0 -69
  400. package/dist/esm/worker/messageBus.js +0 -470
  401. package/dist/types/adapters/asyncStorage.d.ts +0 -2
  402. package/dist/types/adapters/expo.d.ts +0 -4
  403. package/dist/types/adapters/fileSystem.d.ts +0 -2
  404. package/dist/types/adapters/indexedDB.d.ts +0 -2
  405. package/dist/types/adapters/localStorage.d.ts +0 -2
  406. package/dist/types/arkfee/celenv.d.ts +0 -25
  407. package/dist/types/arkfee/estimator.d.ts +0 -49
  408. package/dist/types/arkfee/index.d.ts +0 -2
  409. package/dist/types/arkfee/types.d.ts +0 -38
  410. package/dist/types/arknote/index.d.ts +0 -84
  411. package/dist/types/bip322/index.d.ts +0 -55
  412. package/dist/types/contracts/arkcontract.d.ts +0 -99
  413. package/dist/types/contracts/contractManager.d.ts +0 -411
  414. package/dist/types/contracts/contractWatcher.d.ts +0 -217
  415. package/dist/types/contracts/handlers/default.d.ts +0 -19
  416. package/dist/types/contracts/handlers/delegate.d.ts +0 -21
  417. package/dist/types/contracts/handlers/helpers.d.ts +0 -19
  418. package/dist/types/contracts/handlers/index.d.ts +0 -7
  419. package/dist/types/contracts/handlers/registry.d.ts +0 -65
  420. package/dist/types/contracts/handlers/vhtlc.d.ts +0 -32
  421. package/dist/types/contracts/index.d.ts +0 -14
  422. package/dist/types/contracts/types.d.ts +0 -250
  423. package/dist/types/contracts/vtxoOwnership.d.ts +0 -33
  424. package/dist/types/extension/asset/assetGroup.d.ts +0 -119
  425. package/dist/types/extension/asset/assetId.d.ts +0 -83
  426. package/dist/types/extension/asset/assetInput.d.ts +0 -64
  427. package/dist/types/extension/asset/assetOutput.d.ts +0 -54
  428. package/dist/types/extension/asset/assetRef.d.ts +0 -91
  429. package/dist/types/extension/asset/index.d.ts +0 -8
  430. package/dist/types/extension/asset/metadata.d.ts +0 -52
  431. package/dist/types/extension/asset/packet.d.ts +0 -41
  432. package/dist/types/extension/asset/types.d.ts +0 -16
  433. package/dist/types/extension/asset/utils.d.ts +0 -21
  434. package/dist/types/extension/index.d.ts +0 -56
  435. package/dist/types/extension/packet.d.ts +0 -21
  436. package/dist/types/forfeit.d.ts +0 -18
  437. package/dist/types/identity/descriptor.d.ts +0 -61
  438. package/dist/types/identity/descriptorProvider.d.ts +0 -42
  439. package/dist/types/identity/hdCapableIdentity.d.ts +0 -71
  440. package/dist/types/identity/index.d.ts +0 -57
  441. package/dist/types/identity/seedIdentity.d.ts +0 -270
  442. package/dist/types/identity/serialize.d.ts +0 -96
  443. package/dist/types/identity/singleKey.d.ts +0 -62
  444. package/dist/types/identity/staticDescriptorProvider.d.ts +0 -18
  445. package/dist/types/index.d.ts +0 -59
  446. package/dist/types/intent/index.d.ts +0 -86
  447. package/dist/types/musig2/index.d.ts +0 -4
  448. package/dist/types/musig2/keys.d.ts +0 -9
  449. package/dist/types/musig2/nonces.d.ts +0 -14
  450. package/dist/types/musig2/sign.d.ts +0 -27
  451. package/dist/types/networks.d.ts +0 -16
  452. package/dist/types/providers/ark.d.ts +0 -369
  453. package/dist/types/providers/delegator.d.ts +0 -82
  454. package/dist/types/providers/electrum.d.ts +0 -312
  455. package/dist/types/providers/errors.d.ts +0 -13
  456. package/dist/types/providers/expoArk.d.ts +0 -22
  457. package/dist/types/providers/expoIndexer.d.ts +0 -18
  458. package/dist/types/providers/expoUtils.d.ts +0 -18
  459. package/dist/types/providers/indexer.d.ts +0 -301
  460. package/dist/types/providers/onchain.d.ts +0 -148
  461. package/dist/types/providers/utils.d.ts +0 -12
  462. package/dist/types/repositories/contractRepository.d.ts +0 -32
  463. package/dist/types/repositories/inMemory/contractRepository.d.ts +0 -17
  464. package/dist/types/repositories/inMemory/walletRepository.d.ts +0 -29
  465. package/dist/types/repositories/index.d.ts +0 -9
  466. package/dist/types/repositories/indexedDB/contractRepository.d.ts +0 -21
  467. package/dist/types/repositories/indexedDB/db.d.ts +0 -4
  468. package/dist/types/repositories/indexedDB/manager.d.ts +0 -25
  469. package/dist/types/repositories/indexedDB/schema.d.ts +0 -9
  470. package/dist/types/repositories/indexedDB/walletRepository.d.ts +0 -28
  471. package/dist/types/repositories/indexedDB/websqlAdapter.d.ts +0 -49
  472. package/dist/types/repositories/migrations/contractRepositoryImpl.d.ts +0 -24
  473. package/dist/types/repositories/migrations/fromStorageAdapter.d.ts +0 -19
  474. package/dist/types/repositories/migrations/walletRepositoryImpl.d.ts +0 -27
  475. package/dist/types/repositories/realm/contractRepository.d.ts +0 -24
  476. package/dist/types/repositories/realm/index.d.ts +0 -4
  477. package/dist/types/repositories/realm/types.d.ts +0 -16
  478. package/dist/types/repositories/realm/walletRepository.d.ts +0 -34
  479. package/dist/types/repositories/scriptFromAddress.d.ts +0 -9
  480. package/dist/types/repositories/serialization.d.ts +0 -65
  481. package/dist/types/repositories/sqlite/contractRepository.d.ts +0 -33
  482. package/dist/types/repositories/sqlite/index.d.ts +0 -3
  483. package/dist/types/repositories/sqlite/types.d.ts +0 -18
  484. package/dist/types/repositories/walletRepository.d.ts +0 -72
  485. package/dist/types/script/address.d.ts +0 -67
  486. package/dist/types/script/base.d.ts +0 -105
  487. package/dist/types/script/default.d.ts +0 -44
  488. package/dist/types/script/delegate.d.ts +0 -40
  489. package/dist/types/script/tapscript.d.ts +0 -169
  490. package/dist/types/script/vhtlc.d.ts +0 -66
  491. package/dist/types/tree/signingSession.d.ts +0 -37
  492. package/dist/types/tree/txTree.d.ts +0 -28
  493. package/dist/types/tree/validation.d.ts +0 -15
  494. package/dist/types/utils/anchor.d.ts +0 -19
  495. package/dist/types/utils/arkTransaction.d.ts +0 -49
  496. package/dist/types/utils/bip21.d.ts +0 -38
  497. package/dist/types/utils/syncCursors.d.ts +0 -60
  498. package/dist/types/utils/timelock.d.ts +0 -9
  499. package/dist/types/utils/transaction.d.ts +0 -13
  500. package/dist/types/utils/transactionHistory.d.ts +0 -15
  501. package/dist/types/utils/txSizeEstimator.d.ts +0 -40
  502. package/dist/types/utils/unknownFields.d.ts +0 -83
  503. package/dist/types/wallet/asset-manager.d.ts +0 -69
  504. package/dist/types/wallet/asset.d.ts +0 -21
  505. package/dist/types/wallet/batch.d.ts +0 -107
  506. package/dist/types/wallet/delegator.d.ts +0 -48
  507. package/dist/types/wallet/expo/background.d.ts +0 -66
  508. package/dist/types/wallet/expo/index.d.ts +0 -4
  509. package/dist/types/wallet/expo/wallet.d.ts +0 -99
  510. package/dist/types/wallet/hdDescriptorProvider.d.ts +0 -114
  511. package/dist/types/wallet/index.d.ts +0 -789
  512. package/dist/types/wallet/inputSignerRouter.d.ts +0 -35
  513. package/dist/types/wallet/onchain.d.ts +0 -109
  514. package/dist/types/wallet/ramps.d.ts +0 -64
  515. package/dist/types/wallet/serviceWorker/wallet-message-handler.d.ts +0 -543
  516. package/dist/types/wallet/serviceWorker/wallet.d.ts +0 -248
  517. package/dist/types/wallet/signingErrors.d.ts +0 -19
  518. package/dist/types/wallet/unroll.d.ts +0 -114
  519. package/dist/types/wallet/utils.d.ts +0 -36
  520. package/dist/types/wallet/validation.d.ts +0 -24
  521. package/dist/types/wallet/vtxo-manager.d.ts +0 -476
  522. package/dist/types/wallet/wallet.d.ts +0 -409
  523. package/dist/types/wallet/walletReceiveRotator.d.ts +0 -306
  524. package/dist/types/worker/browser/service-worker-manager.d.ts +0 -32
  525. package/dist/types/worker/browser/utils.d.ts +0 -17
  526. package/dist/types/worker/errors.d.ts +0 -7
  527. package/dist/types/worker/expo/index.d.ts +0 -7
  528. package/dist/types/worker/expo/processors/contractPollProcessor.d.ts +0 -19
  529. package/dist/types/worker/expo/processors/index.d.ts +0 -1
  530. package/dist/types/worker/expo/taskQueue.d.ts +0 -50
  531. package/dist/types/worker/expo/taskRunner.d.ts +0 -66
  532. package/dist/types/worker/messageBus.d.ts +0 -189
@@ -1,2195 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Wallet = exports.ReadonlyWallet = exports.MissingSigningDescriptorError = exports.DescriptorSigningProviderMissingError = exports.getArkadeServerUrl = void 0;
4
- exports.selectVirtualCoins = selectVirtualCoins;
5
- exports.waitForIncomingFunds = waitForIncomingFunds;
6
- const base_1 = require("@scure/base");
7
- const payment_js_1 = require("@scure/btc-signer/payment.js");
8
- const btc_signer_1 = require("@scure/btc-signer");
9
- const utils_js_1 = require("@scure/btc-signer/utils.js");
10
- const address_1 = require("../script/address");
11
- const default_1 = require("../script/default");
12
- const networks_1 = require("../networks");
13
- const onchain_1 = require("../providers/onchain");
14
- const ark_1 = require("../providers/ark");
15
- const forfeit_1 = require("../forfeit");
16
- const validation_1 = require("../tree/validation");
17
- const validation_2 = require("./validation");
18
- const _1 = require(".");
19
- const asset_1 = require("./asset");
20
- const base_2 = require("../script/base");
21
- const tapscript_1 = require("../script/tapscript");
22
- const arkTransaction_1 = require("../utils/arkTransaction");
23
- const vtxo_manager_1 = require("./vtxo-manager");
24
- const arknote_1 = require("../arknote");
25
- const intent_1 = require("../intent");
26
- const indexer_1 = require("../providers/indexer");
27
- const utils_1 = require("./utils");
28
- const errors_1 = require("../providers/errors");
29
- const batch_1 = require("./batch");
30
- const arkfee_1 = require("../arkfee");
31
- const transactionHistory_1 = require("../utils/transactionHistory");
32
- const asset_manager_1 = require("./asset-manager");
33
- const extension_1 = require("../extension");
34
- const delegate_1 = require("../script/delegate");
35
- const delegator_1 = require("./delegator");
36
- const repositories_1 = require("../repositories");
37
- const contractManager_1 = require("../contracts/contractManager");
38
- const handlers_1 = require("../contracts/handlers");
39
- const timelock_1 = require("../utils/timelock");
40
- const syncCursors_1 = require("../utils/syncCursors");
41
- const vtxoOwnership_1 = require("../contracts/vtxoOwnership");
42
- const walletReceiveRotator_1 = require("./walletReceiveRotator");
43
- const inputSignerRouter_1 = require("./inputSignerRouter");
44
- const signingErrors_1 = require("./signingErrors");
45
- Object.defineProperty(exports, "DescriptorSigningProviderMissingError", { enumerable: true, get: function () { return signingErrors_1.DescriptorSigningProviderMissingError; } });
46
- Object.defineProperty(exports, "MissingSigningDescriptorError", { enumerable: true, get: function () { return signingErrors_1.MissingSigningDescriptorError; } });
47
- const getArkadeServerUrl = ({ arkServerUrl, }) => arkServerUrl || _1.DEFAULT_ARKADE_SERVER_URL;
48
- exports.getArkadeServerUrl = getArkadeServerUrl;
49
- // Build per-input jobs for an intent proof. Index 0 of the proof is a
50
- // synthetic BIP-322 toSpend reference whose witnessUtxo.script mirrors
51
- // coin[0]'s pkScript, so we map it to the same source contract as
52
- // coin[0]; coins 0..N-1 then map to proof inputs 1..N.
53
- function intentProofJobs(coins) {
54
- if (coins.length === 0)
55
- return [];
56
- const coinJobs = coins.map((coin, i) => ({
57
- index: i + 1,
58
- lookupScript: base_2.VtxoScript.decode(coin.tapTree).pkScript,
59
- }));
60
- return [{ index: 0, lookupScript: coinJobs[0].lookupScript }, ...coinJobs];
61
- }
62
- // Built-in ArkProvider implementations (Rest/Expo) expose `serverUrl`,
63
- // but the interface itself does not declare a URL accessor — so this is a
64
- // structural read that returns undefined for custom implementations.
65
- function extractArkProviderUrl(provider) {
66
- const serverUrl = provider.serverUrl;
67
- return typeof serverUrl === "string" && serverUrl.length > 0
68
- ? serverUrl
69
- : undefined;
70
- }
71
- // Historical unilateral exit delay for mainnet (~7 days in seconds).
72
- // Kept so existing wallets can still discover and spend VTXOs sent to the
73
- // legacy address after arkd starts advertising a different delay.
74
- const MAINNET_UNILATERAL_EXIT_DELAY = 605184n;
75
- function delayToTimelock(delay) {
76
- return {
77
- value: delay,
78
- type: delay < 512n ? "blocks" : "seconds",
79
- };
80
- }
81
- function dedupeTimelocks(timelocks) {
82
- const seen = new Set();
83
- const deduped = [];
84
- for (const timelock of timelocks) {
85
- const sequence = (0, timelock_1.timelockToSequence)(timelock).toString();
86
- if (seen.has(sequence))
87
- continue;
88
- seen.add(sequence);
89
- deduped.push(timelock);
90
- }
91
- return deduped;
92
- }
93
- /**
94
- * Type guard function to check if an identity has a toReadonly method.
95
- */
96
- function hasToReadonly(identity) {
97
- return (typeof identity === "object" &&
98
- identity !== null &&
99
- "toReadonly" in identity &&
100
- typeof identity.toReadonly === "function");
101
- }
102
- class ReadonlyWallet {
103
- get assetManager() {
104
- return this._assetManager;
105
- }
106
- constructor(identity, network, onchainProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, dustAmount, walletRepository, contractRepository, delegatorProvider, watcherConfig, walletContractTimelocks) {
107
- this.identity = identity;
108
- this.network = network;
109
- this.onchainProvider = onchainProvider;
110
- this.indexerProvider = indexerProvider;
111
- this.arkServerPublicKey = arkServerPublicKey;
112
- this.boardingTapscript = boardingTapscript;
113
- this.dustAmount = dustAmount;
114
- this.walletRepository = walletRepository;
115
- this.contractRepository = contractRepository;
116
- this.delegatorProvider = delegatorProvider;
117
- // Outpoints ("txid:vout") committed to an in-flight settle/send. Filtered
118
- // from getVtxos() so concurrent callers (UI, VtxoManager auto-renewal,
119
- // another send/settle racing the _txLock) can't reselect coins that are
120
- // already on their way out. The set is in-memory only: a process crash
121
- // clears it, and a stale entry only hides a VTXO (never spends one).
122
- this._pendingSpendOutpoints = new Set();
123
- // Guard: detect identity/server network mismatch for descriptor-based identities.
124
- // This duplicates the check in setupWalletConfig() so that subclasses
125
- // bypassing the factory still get the safety net.
126
- if ("descriptor" in identity) {
127
- const descriptor = identity.descriptor;
128
- const identityIsMainnet = !descriptor.includes("tpub");
129
- const serverIsMainnet = network.bech32 === "bc";
130
- if (identityIsMainnet !== serverIsMainnet) {
131
- throw new Error(`Network mismatch: identity uses ${identityIsMainnet ? "mainnet" : "testnet"} derivation ` +
132
- `but wallet network is ${serverIsMainnet ? "mainnet" : "testnet"}. ` +
133
- `Create identity with { isMainnet: ${serverIsMainnet} } to match.`);
134
- }
135
- }
136
- this._offchainTapscript = offchainTapscript;
137
- this.watcherConfig = watcherConfig;
138
- this._assetManager = new asset_manager_1.ReadonlyAssetManager(this.indexerProvider);
139
- // Defensive for direct-construction callers; setupWalletConfig already
140
- // passes a deduped list through the public create() factories.
141
- this.walletContractTimelocks =
142
- walletContractTimelocks && walletContractTimelocks.length > 0
143
- ? dedupeTimelocks(walletContractTimelocks)
144
- : [
145
- this.offchainTapscript.options.csvTimelock ??
146
- default_1.DefaultVtxo.Script.DEFAULT_TIMELOCK,
147
- ];
148
- }
149
- /**
150
- * Currently-active receive tapscript. Read-only from the outside;
151
- * mutated only via {@link Wallet.setOffchainTapscriptForRotation}
152
- * by {@link WalletReceiveRotator.rotate}.
153
- */
154
- get offchainTapscript() {
155
- return this._offchainTapscript;
156
- }
157
- /**
158
- * Protected helper to set up shared wallet configuration.
159
- * Extracts common logic used by both ReadonlyWallet.create() and Wallet.create().
160
- */
161
- static async setupWalletConfig(config, pubKey) {
162
- const arkadeServerUrl = (0, exports.getArkadeServerUrl)(config);
163
- // Use provided arkProvider instance or create a new one from arkServerUrl
164
- const arkProvider = config.arkProvider ?? new ark_1.RestArkProvider(arkadeServerUrl);
165
- // Resolve the indexer provider. If a full instance is supplied, use it
166
- // directly. Otherwise pick a URL with priority:
167
- // 1. explicit config.indexerUrl
168
- // 2. URL derived from the injected arkProvider (so a custom
169
- // arkProvider does not silently pair with the public default)
170
- // 3. arkadeServerUrl (only when no custom arkProvider was injected)
171
- let indexerProvider = config.indexerProvider;
172
- if (!indexerProvider) {
173
- let indexerUrl = config.indexerUrl;
174
- if (!indexerUrl) {
175
- if (config.arkProvider) {
176
- const derived = extractArkProviderUrl(config.arkProvider);
177
- if (!derived) {
178
- throw new Error("indexerUrl is required when arkProvider is provided without a discoverable serverUrl");
179
- }
180
- indexerUrl = derived;
181
- }
182
- else {
183
- indexerUrl = arkadeServerUrl;
184
- }
185
- }
186
- indexerProvider = new indexer_1.RestIndexerProvider(indexerUrl);
187
- }
188
- const info = await arkProvider.getInfo();
189
- const network = (0, networks_1.getNetwork)(info.network);
190
- // Guard: detect identity/server network mismatch for seed-based identities.
191
- // A mainnet descriptor (xpub, coin type 0) connected to a testnet server
192
- // (or vice versa) means wrong derivation path → wrong keys → potential fund loss.
193
- if ("descriptor" in config.identity) {
194
- const descriptor = config.identity.descriptor;
195
- const identityIsMainnet = !descriptor.includes("tpub");
196
- const serverIsMainnet = info.network === "bitcoin";
197
- if (identityIsMainnet && !serverIsMainnet) {
198
- throw new Error(`Network mismatch: identity uses mainnet derivation (coin type 0) ` +
199
- `but the Arkade server is on ${info.network}. ` +
200
- `Create identity with { isMainnet: false } to use testnet derivation.`);
201
- }
202
- if (!identityIsMainnet && serverIsMainnet) {
203
- throw new Error(`Network mismatch: identity uses testnet derivation (coin type 1) ` +
204
- `but the Arkade server is on mainnet. ` +
205
- `Create identity with { isMainnet: true } or omit isMainnet (defaults to mainnet).`);
206
- }
207
- }
208
- // Extract esploraUrl from provider if not explicitly provided
209
- const esploraUrl = config.esploraUrl || onchain_1.ESPLORA_URL[info.network];
210
- // Use provided onchainProvider instance or create a new one
211
- const onchainProvider = config.onchainProvider || new onchain_1.EsploraProvider(esploraUrl);
212
- // validate unilateral exit timelock passed in config if any
213
- if (config.exitTimelock) {
214
- const { value, type } = config.exitTimelock;
215
- if ((value < 512n && type !== "blocks") ||
216
- (value >= 512n && type !== "seconds")) {
217
- throw new Error("invalid exitTimelock");
218
- }
219
- }
220
- const arkdExitTimelock = delayToTimelock(info.unilateralExitDelay);
221
- // create unilateral exit timelock
222
- const exitTimelock = config.exitTimelock ?? arkdExitTimelock;
223
- const walletContractTimelocks = config.exitTimelock
224
- ? [exitTimelock]
225
- : dedupeTimelocks([
226
- arkdExitTimelock,
227
- ...(info.network === "bitcoin"
228
- ? [delayToTimelock(MAINNET_UNILATERAL_EXIT_DELAY)]
229
- : []),
230
- ]);
231
- // validate boarding timelock passed in config if any
232
- if (config.boardingTimelock) {
233
- const { value, type } = config.boardingTimelock;
234
- if ((value < 512n && type !== "blocks") ||
235
- (value >= 512n && type !== "seconds")) {
236
- throw new Error("invalid boardingTimelock");
237
- }
238
- }
239
- // create boarding timelock
240
- const boardingTimelock = config.boardingTimelock ?? {
241
- value: info.boardingExitDelay,
242
- type: info.boardingExitDelay < 512n ? "blocks" : "seconds",
243
- };
244
- // Generate tapscripts for offchain and boarding address
245
- const serverPubKey = base_1.hex.decode(info.signerPubkey).slice(1);
246
- const delegatePubKey = config.delegatorProvider
247
- ? await config.delegatorProvider
248
- .getDelegateInfo()
249
- .then((info) => base_1.hex.decode(info.pubkey).slice(1))
250
- : undefined;
251
- const offchainOptions = {
252
- pubKey,
253
- serverPubKey,
254
- csvTimelock: exitTimelock,
255
- };
256
- const offchainTapscript = !delegatePubKey
257
- ? new default_1.DefaultVtxo.Script(offchainOptions)
258
- : new delegate_1.DelegateVtxo.Script({ ...offchainOptions, delegatePubKey });
259
- const boardingTapscript = new default_1.DefaultVtxo.Script({
260
- ...offchainOptions,
261
- csvTimelock: boardingTimelock,
262
- });
263
- const walletRepository = config.storage?.walletRepository ?? new repositories_1.IndexedDBWalletRepository();
264
- const contractRepository = config.storage?.contractRepository ??
265
- new repositories_1.IndexedDBContractRepository();
266
- return {
267
- arkProvider,
268
- indexerProvider,
269
- onchainProvider,
270
- network,
271
- networkName: info.network,
272
- serverPubKey,
273
- offchainTapscript,
274
- boardingTapscript,
275
- dustAmount: info.dust,
276
- walletRepository,
277
- contractRepository,
278
- info,
279
- delegatorProvider: config.delegatorProvider,
280
- walletContractTimelocks,
281
- };
282
- }
283
- /**
284
- * Create a readonly wallet for querying balances, addresses, and history.
285
- *
286
- * @param config - Readonly wallet configuration
287
- * @returns A readonly wallet instance
288
- */
289
- static async create(config) {
290
- const pubkey = await config.identity.xOnlyPublicKey();
291
- if (!pubkey) {
292
- throw new Error("Invalid configured public key");
293
- }
294
- const setup = await ReadonlyWallet.setupWalletConfig(config, pubkey);
295
- return new ReadonlyWallet(config.identity, setup.network, setup.onchainProvider, setup.indexerProvider, setup.serverPubKey, setup.offchainTapscript, setup.boardingTapscript, setup.dustAmount, setup.walletRepository, setup.contractRepository, setup.delegatorProvider, config.watcherConfig, setup.walletContractTimelocks);
296
- }
297
- get arkAddress() {
298
- return this.offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
299
- }
300
- /**
301
- * Get the pkScript hex for the wallet's primary offchain address.
302
- * For the full wallet-owned script set registered in ContractManager, use getWalletScripts().
303
- */
304
- get defaultContractScript() {
305
- return base_1.hex.encode(this.offchainTapscript.pkScript);
306
- }
307
- /** Returns the wallet's Arkade address. */
308
- async getAddress() {
309
- return this.arkAddress.encode();
310
- }
311
- /** Returns the onchain boarding address used to move funds into Arkade. */
312
- async getBoardingAddress() {
313
- return this.boardingTapscript.onchainAddress(this.network);
314
- }
315
- /**
316
- * Return the wallet's combined onchain and offchain balances.
317
- */
318
- async getBalance() {
319
- const [boardingUtxos, vtxos] = await Promise.all([
320
- this.getBoardingUtxos(),
321
- this.getVtxos(),
322
- ]);
323
- // boarding
324
- let confirmed = 0;
325
- let unconfirmed = 0;
326
- for (const utxo of boardingUtxos) {
327
- if (utxo.status.confirmed) {
328
- confirmed += utxo.value;
329
- }
330
- else {
331
- unconfirmed += utxo.value;
332
- }
333
- }
334
- // offchain
335
- let settled = 0;
336
- let preconfirmed = 0;
337
- let recoverable = 0;
338
- settled = vtxos
339
- .filter((coin) => coin.virtualStatus.state === "settled")
340
- .reduce((sum, coin) => sum + coin.value, 0);
341
- preconfirmed = vtxos
342
- .filter((coin) => coin.virtualStatus.state === "preconfirmed")
343
- .reduce((sum, coin) => sum + coin.value, 0);
344
- recoverable = vtxos
345
- .filter((coin) => (0, _1.isSpendable)(coin) && coin.virtualStatus.state === "swept")
346
- .reduce((sum, coin) => sum + coin.value, 0);
347
- const totalBoarding = confirmed + unconfirmed;
348
- const totalOffchain = settled + preconfirmed + recoverable;
349
- // aggregate asset balances from spendable virtual outputs
350
- const assetBalances = new Map();
351
- for (const vtxo of vtxos) {
352
- if (!(0, _1.isSpendable)(vtxo))
353
- continue;
354
- if (vtxo.assets) {
355
- for (const a of vtxo.assets) {
356
- const current = assetBalances.get(a.assetId) ?? 0n;
357
- assetBalances.set(a.assetId, current + a.amount);
358
- }
359
- }
360
- }
361
- const assets = Array.from(assetBalances.entries()).map(([assetId, amount]) => ({
362
- assetId,
363
- amount,
364
- }));
365
- return {
366
- boarding: {
367
- confirmed,
368
- unconfirmed,
369
- total: totalBoarding,
370
- },
371
- settled,
372
- preconfirmed,
373
- available: settled + preconfirmed,
374
- recoverable,
375
- total: totalBoarding + totalOffchain,
376
- assets,
377
- };
378
- }
379
- /**
380
- * Return virtual outputs tracked by the wallet.
381
- *
382
- * @param filter - Optional flags controlling whether recoverable or unrolled VTXOs are included
383
- */
384
- async getVtxos(filter) {
385
- const f = filter ?? { withRecoverable: true, withUnrolled: false };
386
- const contractManager = await this.getContractManager();
387
- const vtxos = await contractManager.getContractsWithVtxos();
388
- return vtxos
389
- .flatMap((_) => _.vtxos)
390
- .filter((vtxo) => {
391
- if (this._pendingSpendOutpoints.has(`${vtxo.txid}:${vtxo.vout}`)) {
392
- return false;
393
- }
394
- if ((0, _1.isSpendable)(vtxo)) {
395
- if (!f.withRecoverable &&
396
- ((0, _1.isRecoverable)(vtxo) || (0, _1.isExpired)(vtxo))) {
397
- return false;
398
- }
399
- return true;
400
- }
401
- return !!(f.withUnrolled && vtxo.isUnrolled);
402
- });
403
- }
404
- /**
405
- * Return wallet transaction history derived from Arkade state and boarding transactions.
406
- */
407
- async getTransactionHistory() {
408
- const contractManager = await this.getContractManager();
409
- const response = await contractManager.getContractsWithVtxos();
410
- const allVtxos = response.flatMap((_) => _.vtxos);
411
- const { boardingTxs, commitmentsToIgnore } = await this.getBoardingTxs();
412
- const getTxCreatedAt = (txid) => this.indexerProvider
413
- .getVtxos({ outpoints: [{ txid, vout: 0 }] })
414
- .then((res) => res.vtxos[0]?.createdAt.getTime());
415
- return (0, transactionHistory_1.buildTransactionHistory)(allVtxos, boardingTxs, commitmentsToIgnore, getTxCreatedAt);
416
- }
417
- /**
418
- * Clear the global VTXO sync cursor, forcing a full re-bootstrap on next sync.
419
- * Useful for recovery after indexer reprocessing or debugging.
420
- */
421
- async clearSyncCursor() {
422
- await (0, syncCursors_1.clearSyncCursor)(this.walletRepository);
423
- }
424
- /**
425
- * Build a transaction history view for the wallet's boarding address.
426
- */
427
- async getBoardingTxs() {
428
- const utxos = [];
429
- const commitmentsToIgnore = new Set();
430
- const boardingAddress = await this.getBoardingAddress();
431
- const txs = await this.onchainProvider.getTransactions(boardingAddress);
432
- const outspendCache = new Map();
433
- for (const tx of txs) {
434
- for (let i = 0; i < tx.vout.length; i++) {
435
- const vout = tx.vout[i];
436
- if (vout.scriptpubkey_address === boardingAddress) {
437
- let spentStatuses = outspendCache.get(tx.txid);
438
- if (!spentStatuses) {
439
- spentStatuses =
440
- await this.onchainProvider.getTxOutspends(tx.txid);
441
- outspendCache.set(tx.txid, spentStatuses);
442
- }
443
- const spentStatus = spentStatuses[i];
444
- if (spentStatus?.spent) {
445
- commitmentsToIgnore.add(spentStatus.txid);
446
- }
447
- utxos.push({
448
- txid: tx.txid,
449
- vout: i,
450
- value: Number(vout.value),
451
- status: {
452
- confirmed: tx.status.confirmed,
453
- block_time: tx.status.block_time,
454
- },
455
- isUnrolled: true,
456
- virtualStatus: {
457
- state: spentStatus?.spent ? "spent" : "settled",
458
- commitmentTxIds: spentStatus?.spent
459
- ? [spentStatus.txid]
460
- : undefined,
461
- },
462
- createdAt: tx.status.confirmed
463
- ? new Date(tx.status.block_time * 1000)
464
- : new Date(0),
465
- script: base_1.hex.encode(this.boardingTapscript.pkScript),
466
- });
467
- }
468
- }
469
- }
470
- const unconfirmedTxs = [];
471
- const confirmedTxs = [];
472
- for (const utxo of utxos) {
473
- const tx = {
474
- key: {
475
- boardingTxid: utxo.txid,
476
- commitmentTxid: "",
477
- arkTxid: "",
478
- },
479
- amount: utxo.value,
480
- type: _1.TxType.TxReceived,
481
- settled: utxo.virtualStatus.state === "spent",
482
- createdAt: utxo.status.block_time
483
- ? new Date(utxo.status.block_time * 1000).getTime()
484
- : 0,
485
- };
486
- if (!utxo.status.block_time) {
487
- unconfirmedTxs.push(tx);
488
- }
489
- else {
490
- confirmedTxs.push(tx);
491
- }
492
- }
493
- return {
494
- boardingTxs: [...unconfirmedTxs, ...confirmedTxs],
495
- commitmentsToIgnore,
496
- };
497
- }
498
- /**
499
- * Fetch and cache onchain inputs (UTXOs) received at the boarding address.
500
- */
501
- async getBoardingUtxos() {
502
- const boardingAddress = await this.getBoardingAddress();
503
- const boardingUtxos = await this.onchainProvider.getCoins(boardingAddress);
504
- const utxos = boardingUtxos.map((utxo) => {
505
- return (0, utils_1.extendCoin)(this, utxo);
506
- });
507
- // Save boarding inputs using unified repository
508
- await this.walletRepository.saveUtxos(boardingAddress, utxos);
509
- return utxos;
510
- }
511
- /**
512
- * Subscribe to onchain and offchain notifications for newly received funds.
513
- *
514
- * @param eventCallback - Callback invoked when matching funds are detected
515
- * @returns A function that stops the subscriptions
516
- */
517
- async notifyIncomingFunds(eventCallback) {
518
- const arkAddress = await this.getAddress();
519
- const boardingAddress = await this.getBoardingAddress();
520
- let onchainStopFunc;
521
- let indexerStopFunc;
522
- if (this.onchainProvider && boardingAddress) {
523
- const findVoutOnTx = (tx) => {
524
- return tx.vout.findIndex((v) => v.scriptpubkey_address === boardingAddress);
525
- };
526
- onchainStopFunc = await this.onchainProvider.watchAddresses([boardingAddress], (txs) => {
527
- // find all onchain outputs belonging to our boarding address
528
- const coins = txs
529
- // filter txs where address is in output
530
- .filter((tx) => findVoutOnTx(tx) !== -1)
531
- // return boarding input as Coin
532
- .map((tx) => {
533
- const { txid, status } = tx;
534
- const vout = findVoutOnTx(tx);
535
- const value = Number(tx.vout[vout].value);
536
- return { txid, vout, value, status };
537
- });
538
- // and notify via callback
539
- eventCallback({
540
- type: "utxo",
541
- coins,
542
- });
543
- });
544
- }
545
- if (this.indexerProvider && arkAddress) {
546
- // Share the ContractWatcher's single subscription instead of
547
- // opening a second SSE stream.
548
- const cm = await this.getContractManager();
549
- // Serialize annotation+notification: parallel `annotateVtxos`
550
- // awaits could resolve out of order and deliver eventCallback
551
- // calls in the wrong sequence (e.g. `vtxo_spent` before its
552
- // matching `vtxo_received`).
553
- let annotationQueue = Promise.resolve();
554
- indexerStopFunc = cm.onContractEvent((event) => {
555
- if (event.type !== "vtxo_received" &&
556
- event.type !== "vtxo_spent") {
557
- return;
558
- }
559
- if (event.contract.type !== "default" &&
560
- event.contract.type !== "delegate") {
561
- return;
562
- }
563
- // `event.vtxos` carries placeholder tapscript fields from
564
- // the watcher; `annotateVtxos` fills them in.
565
- annotationQueue = annotationQueue.then(async () => {
566
- try {
567
- const annotated = await cm.annotateVtxos(event.vtxos);
568
- eventCallback({
569
- type: "vtxo",
570
- newVtxos: event.type === "vtxo_received" ? annotated : [],
571
- spentVtxos: event.type === "vtxo_spent" ? annotated : [],
572
- });
573
- }
574
- catch (error) {
575
- console.warn("Dropping subscription update after annotation failed; next sync will reconcile:", error);
576
- }
577
- });
578
- });
579
- }
580
- const stopFunc = () => {
581
- onchainStopFunc?.();
582
- indexerStopFunc?.();
583
- };
584
- return stopFunc;
585
- }
586
- /** Fetch Arkade transaction ids that are still pending final settlement. */
587
- async fetchPendingTxs() {
588
- // get non-swept virtual outputs, rely on the indexer only in case DB doesn't have the right state
589
- const scripts = await this.getWalletScripts();
590
- let { vtxos } = await this.indexerProvider.getVtxos({
591
- scripts,
592
- });
593
- return vtxos
594
- .filter((vtxo) => vtxo.virtualStatus.state !== "swept" &&
595
- vtxo.virtualStatus.state !== "settled" &&
596
- vtxo.arkTxId !== undefined)
597
- .map((_) => _.arkTxId);
598
- }
599
- // ========================================================================
600
- // Multi-script support (default + delegate addresses)
601
- // ========================================================================
602
- /**
603
- * Get all pkScript hex strings for the wallet's own addresses
604
- * (both delegate and non-delegate, current and historical).
605
- */
606
- async getWalletScripts() {
607
- const manager = await this.getContractManager();
608
- const contracts = await manager.getContracts({
609
- type: ["default", "delegate"],
610
- });
611
- return contracts.map((c) => c.script);
612
- }
613
- /**
614
- * Build a map of scriptHex → VtxoScript for all wallet contracts,
615
- * so virtual outputs can be extended with the correct tapscript per contract.
616
- */
617
- async getScriptMap() {
618
- const map = new Map();
619
- const manager = await this.getContractManager();
620
- const contracts = await manager.getContracts({
621
- type: ["default", "delegate"],
622
- });
623
- for (const contract of contracts) {
624
- if (map.has(contract.script))
625
- continue;
626
- const handler = handlers_1.contractHandlers.get(contract.type);
627
- if (handler) {
628
- const script = handler.createScript(contract.params);
629
- map.set(contract.script, script);
630
- }
631
- }
632
- return map;
633
- }
634
- // ========================================================================
635
- // Contract Management
636
- // ========================================================================
637
- /**
638
- * Get the ContractManager for managing contracts including the wallet's default address.
639
- *
640
- * The ContractManager handles:
641
- * - The wallet's default receiving address (as a "default" contract)
642
- * - External contracts (Boltz swaps, HTLCs, etc.)
643
- * - Multi-contract watching with resilient connections
644
- *
645
- * @example
646
- * ```typescript
647
- * const manager = await wallet.getContractManager();
648
- *
649
- * // Create a contract for a Boltz swap
650
- * const contract = await manager.createContract({
651
- * label: "Boltz Swap",
652
- * type: "vhtlc",
653
- * params: { ... },
654
- * script: swapScript,
655
- * address: swapAddress,
656
- * });
657
- *
658
- * // Start watching for events (includes wallet's default address)
659
- * const stop = await manager.onContractEvent((event) => {
660
- * console.log(`${event.type} on ${event.contractScript}`);
661
- * });
662
- * ```
663
- */
664
- async getContractManager() {
665
- // Return existing manager if already initialized
666
- if (this._contractManager) {
667
- return this._contractManager;
668
- }
669
- // If initialization is in progress, wait for it
670
- if (this._contractManagerInitializing) {
671
- return this._contractManagerInitializing;
672
- }
673
- // Start initialization and store the promise
674
- this._contractManagerInitializing = this.initializeContractManager();
675
- try {
676
- const manager = await this._contractManagerInitializing;
677
- this._contractManager = manager;
678
- return manager;
679
- }
680
- catch (error) {
681
- // Clear the initializing promise so subsequent calls can retry
682
- this._contractManagerInitializing = undefined;
683
- throw error;
684
- }
685
- finally {
686
- // Clear the initializing promise after completion
687
- this._contractManagerInitializing = undefined;
688
- }
689
- }
690
- async initializeContractManager() {
691
- const manager = await contractManager_1.ContractManager.create({
692
- indexerProvider: this.indexerProvider,
693
- contractRepository: this.contractRepository,
694
- walletRepository: this.walletRepository,
695
- watcherConfig: this.watcherConfig,
696
- });
697
- // Register the wallet's baseline always-active contracts: every
698
- // `walletContractTimelocks` entry × {default, delegate-if-enabled}.
699
- // This matrix is bound to INDEX 0 — the identity's x-only pubkey
700
- // — by design: it's the permanent fallback set the wallet wants
701
- // active forever, independent of any HD rotation. Rotated
702
- // display contracts (registered separately by
703
- // {@link WalletReceiveRotator.rotate}) are intentionally
704
- // single-timelock-single-pubkey at the CURRENT arkd delay, and
705
- // get the `metadata.source = WALLET_RECEIVE_SOURCE` tag so the
706
- // next boot can find them. We deliberately do NOT re-register
707
- // the matrix at a rotated pubkey: doing so would dilute the
708
- // "index-0 baseline" guarantee and turn every rotation into a
709
- // multi-timelock matrix expansion on every boot.
710
- const baselinePubkey = await this.identity.xOnlyPublicKey();
711
- for (const csvTimelock of this.walletContractTimelocks) {
712
- const csvTimelockStr = (0, timelock_1.timelockToSequence)(csvTimelock).toString();
713
- const defaultScript = new default_1.DefaultVtxo.Script({
714
- pubKey: baselinePubkey,
715
- serverPubKey: this.offchainTapscript.options.serverPubKey,
716
- csvTimelock,
717
- });
718
- const defaultScriptHex = base_1.hex.encode(defaultScript.pkScript);
719
- await manager.createContract({
720
- type: "default",
721
- params: {
722
- pubKey: base_1.hex.encode(defaultScript.options.pubKey),
723
- serverPubKey: base_1.hex.encode(defaultScript.options.serverPubKey),
724
- csvTimelock: csvTimelockStr,
725
- },
726
- script: defaultScriptHex,
727
- address: defaultScript
728
- .address(this.network.hrp, this.arkServerPublicKey)
729
- .encode(),
730
- state: "active",
731
- });
732
- if (this.offchainTapscript instanceof delegate_1.DelegateVtxo.Script) {
733
- const delegateScript = new delegate_1.DelegateVtxo.Script({
734
- pubKey: baselinePubkey,
735
- serverPubKey: this.offchainTapscript.options.serverPubKey,
736
- delegatePubKey: this.offchainTapscript.options.delegatePubKey,
737
- csvTimelock,
738
- });
739
- const delegateScriptHex = base_1.hex.encode(delegateScript.pkScript);
740
- await manager.createContract({
741
- type: "delegate",
742
- params: {
743
- pubKey: base_1.hex.encode(delegateScript.options.pubKey),
744
- serverPubKey: base_1.hex.encode(delegateScript.options.serverPubKey),
745
- delegatePubKey: base_1.hex.encode(delegateScript.options.delegatePubKey),
746
- csvTimelock: csvTimelockStr,
747
- },
748
- script: delegateScriptHex,
749
- address: delegateScript
750
- .address(this.network.hrp, this.arkServerPublicKey)
751
- .encode(),
752
- state: "active",
753
- });
754
- }
755
- }
756
- return manager;
757
- }
758
- /** Dispose wallet-owned managers and release background resources. */
759
- async dispose() {
760
- const manager = this._contractManager ??
761
- (this._contractManagerInitializing
762
- ? await this._contractManagerInitializing.catch(() => undefined)
763
- : undefined);
764
- manager?.dispose();
765
- this._contractManager = undefined;
766
- this._contractManagerInitializing = undefined;
767
- }
768
- /** Async-dispose hook that forwards to `dispose()`. */
769
- async [Symbol.asyncDispose]() {
770
- await this.dispose();
771
- }
772
- }
773
- exports.ReadonlyWallet = ReadonlyWallet;
774
- /**
775
- * Main wallet implementation for Bitcoin transactions with Arkade protocol support.
776
- * The wallet does not store any data locally and relies on Arkade and onchain
777
- * providers to fetch onchain and virtual outputs.
778
- *
779
- * @example
780
- * ```typescript
781
- * // Create a wallet with URL configuration
782
- * const wallet = await Wallet.create({
783
- * identity: MnemonicIdentity.fromMnemonic('abandon abandon...'),
784
- * arkServerUrl: 'https://arkade.computer',
785
- * esploraUrl: 'https://mempool.space/api'
786
- * });
787
- *
788
- * // Or with custom provider instances (e.g., for Expo/React Native)
789
- * const wallet = await Wallet.create({
790
- * identity: MnemonicIdentity.fromMnemonic('abandon abandon...'),
791
- * arkProvider: new ExpoArkProvider('https://arkade.computer'),
792
- * indexerProvider: new ExpoIndexerProvider('https://arkade.computer'),
793
- * esploraUrl: 'https://mempool.space/api'
794
- * });
795
- *
796
- * // Get addresses
797
- * const arkAddress = await wallet.getAddress();
798
- * const boardingAddress = await wallet.getBoardingAddress();
799
- *
800
- * // Send bitcoin
801
- * const txid = await wallet.send({
802
- * address: 'ark1q...',
803
- * amount: 50000,
804
- * });
805
- * ```
806
- */
807
- class Wallet extends ReadonlyWallet {
808
- /**
809
- * @internal Sole write path for `offchainTapscript` after construction.
810
- * Called by {@link WalletReceiveRotator.rotate} once the rotated
811
- * display contract has been persisted. External code must treat
812
- * `offchainTapscript` as read-only.
813
- */
814
- setOffchainTapscriptForRotation(tapscript) {
815
- this._offchainTapscript = tapscript;
816
- }
817
- _addPendingSpends(inputs) {
818
- for (const input of inputs) {
819
- if ("virtualStatus" in input) {
820
- this._pendingSpendOutpoints.add(`${input.txid}:${input.vout}`);
821
- }
822
- }
823
- }
824
- _removePendingSpends(inputs) {
825
- for (const input of inputs) {
826
- if ("virtualStatus" in input) {
827
- this._pendingSpendOutpoints.delete(`${input.txid}:${input.vout}`);
828
- }
829
- }
830
- }
831
- _withTxLock(fn) {
832
- let release;
833
- const lock = new Promise((r) => (release = r));
834
- const prev = this._txLock;
835
- this._txLock = lock;
836
- return prev.then(async () => {
837
- try {
838
- return await fn();
839
- }
840
- finally {
841
- release();
842
- }
843
- });
844
- }
845
- constructor(identity, network, onchainProvider, arkProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, forfeitPubkey, dustAmount, walletRepository, contractRepository,
846
- /** @deprecated Use settlementConfig */
847
- renewalConfig, delegatorProvider, watcherConfig, settlementConfig, walletContractTimelocks, receiveRotator, descriptorProvider) {
848
- super(identity, network, onchainProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, dustAmount, walletRepository, contractRepository, delegatorProvider, watcherConfig, walletContractTimelocks);
849
- this.arkProvider = arkProvider;
850
- this.serverUnrollScript = serverUnrollScript;
851
- this.forfeitOutputScript = forfeitOutputScript;
852
- this.forfeitPubkey = forfeitPubkey;
853
- this._receiveRotatorInstalled = false;
854
- /**
855
- * Async mutex that serializes all operations submitting VTXOs to the Arkade
856
- * server (`settle`, `send`, `sendBitcoin`). This prevents VtxoManager's
857
- * background renewal from racing with user-initiated transactions for the
858
- * same VTXO inputs.
859
- */
860
- this._txLock = Promise.resolve();
861
- this.identity = identity;
862
- // Backwards-compatible: keep renewalConfig populated for any code reading it
863
- this.renewalConfig = {
864
- enabled: renewalConfig?.enabled ?? false,
865
- ...vtxo_manager_1.DEFAULT_RENEWAL_CONFIG,
866
- ...renewalConfig,
867
- };
868
- // Normalize: prefer settlementConfig, fall back to renewalConfig, default to enabled
869
- if (settlementConfig !== undefined) {
870
- this.settlementConfig = settlementConfig;
871
- }
872
- else if (renewalConfig && this.renewalConfig.enabled) {
873
- this.settlementConfig = {
874
- vtxoThreshold: renewalConfig.thresholdMs
875
- ? renewalConfig.thresholdMs / 1000
876
- : undefined,
877
- };
878
- }
879
- else if (renewalConfig) {
880
- // renewalConfig provided but not enabled → disabled
881
- this.settlementConfig = false;
882
- }
883
- else {
884
- // No config at all → enabled by default
885
- this.settlementConfig = { ...vtxo_manager_1.DEFAULT_SETTLEMENT_CONFIG };
886
- }
887
- this._delegatorManager = delegatorProvider
888
- ? new delegator_1.DelegatorManagerImpl(delegatorProvider, arkProvider, identity)
889
- : undefined;
890
- this._receiveRotator = receiveRotator;
891
- this._descriptorProvider = descriptorProvider;
892
- this._signerRouter = new inputSignerRouter_1.InputSignerRouter({
893
- identity,
894
- contractRepository,
895
- descriptorProvider,
896
- boardingPkScript: boardingTapscript.pkScript,
897
- });
898
- }
899
- get assetManager() {
900
- this._walletAssetManager ?? (this._walletAssetManager = new asset_manager_1.AssetManager(this));
901
- return this._walletAssetManager;
902
- }
903
- async getVtxoManager() {
904
- if (this._vtxoManager) {
905
- return this._vtxoManager;
906
- }
907
- if (this._vtxoManagerInitializing) {
908
- return this._vtxoManagerInitializing;
909
- }
910
- this._vtxoManagerInitializing = Promise.resolve(new vtxo_manager_1.VtxoManager(this, this.renewalConfig, this.settlementConfig));
911
- try {
912
- const manager = await this._vtxoManagerInitializing;
913
- // First-time hookup of the HD rotator: subscribe to
914
- // `vtxo_received` AFTER the contract manager (which is
915
- // initialised inside the VtxoManager construction path) has
916
- // registered the wallet's baseline contracts. The flag
917
- // makes this idempotent across repeated `getVtxoManager`
918
- // calls — install runs at most once per wallet instance.
919
- // Cache the manager and flip the install flag only after
920
- // `install()` resolves; otherwise a failing install would
921
- // leave the manager cached and silently disable HD
922
- // rotation for the lifetime of this wallet.
923
- if (this._receiveRotator && !this._receiveRotatorInstalled) {
924
- try {
925
- await this._receiveRotator.install(this);
926
- }
927
- catch (installErr) {
928
- await manager.dispose();
929
- throw installErr;
930
- }
931
- this._receiveRotatorInstalled = true;
932
- }
933
- this._vtxoManager = manager;
934
- return manager;
935
- }
936
- finally {
937
- this._vtxoManagerInitializing = undefined;
938
- }
939
- }
940
- async dispose() {
941
- // Tear down the rotation subscription + drain in-flight rotations
942
- // first so no late `vtxo_received` event can queue work on a
943
- // disposing wallet, and so any in-flight `createContract` call
944
- // finishes before we dispose the contract manager underneath it.
945
- // A rotator-disposal failure must not abort the rest of
946
- // teardown — the contract manager / super still need to run on
947
- // best-effort, so we capture and rethrow at the end.
948
- let rotatorError;
949
- try {
950
- await this._receiveRotator?.dispose();
951
- }
952
- catch (error) {
953
- rotatorError = error;
954
- }
955
- const manager = this._vtxoManager ??
956
- (this._vtxoManagerInitializing
957
- ? await this._vtxoManagerInitializing.catch(() => undefined)
958
- : undefined);
959
- try {
960
- if (manager) {
961
- await manager.dispose();
962
- }
963
- }
964
- catch {
965
- // best-effort teardown; ensure super.dispose() still runs
966
- }
967
- finally {
968
- this._vtxoManager = undefined;
969
- this._vtxoManagerInitializing = undefined;
970
- await super.dispose();
971
- }
972
- if (rotatorError) {
973
- throw rotatorError;
974
- }
975
- }
976
- /**
977
- * Create a full wallet and initialize its background managers.
978
- *
979
- * @param config - Wallet configuration
980
- * @returns A wallet ready to query balances and send transactions
981
- * @example
982
- * ```typescript
983
- * const wallet = await Wallet.create({
984
- * identity,
985
- * arkServerUrl: 'https://arkade.computer',
986
- * });
987
- * ```
988
- */
989
- static async create(config) {
990
- const pubkey = await config.identity.xOnlyPublicKey();
991
- if (!pubkey) {
992
- throw new Error("Invalid configured public key");
993
- }
994
- const setup = await ReadonlyWallet.setupWalletConfig(config, pubkey);
995
- // Compute Wallet-specific forfeit and unroll scripts
996
- // the serverUnrollScript is the one used to create output scripts of the checkpoint transactions
997
- let serverUnrollScript;
998
- try {
999
- const raw = base_1.hex.decode(setup.info.checkpointTapscript);
1000
- serverUnrollScript = tapscript_1.CSVMultisigTapscript.decode(raw);
1001
- }
1002
- catch (e) {
1003
- throw new Error("Invalid checkpointTapscript from server");
1004
- }
1005
- // parse the server forfeit address
1006
- // server is expecting funds to be sent to this address
1007
- const forfeitPubkey = base_1.hex.decode(setup.info.forfeitPubkey).slice(1);
1008
- const forfeitAddress = (0, btc_signer_1.Address)(setup.network).decode(setup.info.forfeitAddress);
1009
- const forfeitOutputScript = btc_signer_1.OutScript.encode(forfeitAddress);
1010
- // HD wiring (boot path) — resolved via the descriptor provider.
1011
- // The rotator (when present) is handed to the constructor as
1012
- // the last positional arg and `getVtxoManager()` lazily
1013
- // installs its `vtxo_received` subscription on first call,
1014
- // after the contract manager has registered the wallet's
1015
- // baseline contracts.
1016
- const boot = await walletReceiveRotator_1.WalletReceiveRotator.resolveBoot(config, setup);
1017
- const wallet = new Wallet(config.identity, setup.network, setup.onchainProvider, setup.arkProvider, setup.indexerProvider, setup.serverPubKey, boot?.offchainTapscript ?? setup.offchainTapscript, setup.boardingTapscript, serverUnrollScript, forfeitOutputScript, forfeitPubkey, setup.dustAmount, setup.walletRepository, setup.contractRepository, config.renewalConfig, config.delegatorProvider, config.watcherConfig, config.settlementConfig, setup.walletContractTimelocks, boot?.rotator, boot?.provider);
1018
- await wallet.getVtxoManager();
1019
- return wallet;
1020
- }
1021
- /**
1022
- * Convert this wallet to a readonly wallet.
1023
- *
1024
- * @returns A readonly wallet with the same configuration but readonly identity
1025
- * @example
1026
- * ```typescript
1027
- * const wallet = await Wallet.create({ identity: MnemonicIdentity.fromMnemonic('abandon abandon...'), ... });
1028
- * const readonlyWallet = await wallet.toReadonly();
1029
- *
1030
- * // Can query balance and addresses
1031
- * const balance = await readonlyWallet.getBalance();
1032
- * const address = await readonlyWallet.getAddress();
1033
- *
1034
- * // But cannot send transactions (type error)
1035
- * // readonlyWallet.send(...); // TypeScript error
1036
- * ```
1037
- */
1038
- async toReadonly() {
1039
- // Check if the identity has a toReadonly method using type guard
1040
- const readonlyIdentity = hasToReadonly(this.identity)
1041
- ? await this.identity.toReadonly()
1042
- : this.identity; // Identity extends ReadonlyIdentity, so this is safe
1043
- return new ReadonlyWallet(readonlyIdentity, this.network, this.onchainProvider, this.indexerProvider, this.arkServerPublicKey, this.offchainTapscript, this.boardingTapscript, this.dustAmount, this.walletRepository, this.contractRepository, this.delegatorProvider, this.watcherConfig, this.walletContractTimelocks);
1044
- }
1045
- /** Returns the delegator manager when delegation support is configured. */
1046
- async getDelegatorManager() {
1047
- return this._delegatorManager;
1048
- }
1049
- /**
1050
- * Send bitcoin to an Arkade address.
1051
- *
1052
- * @deprecated Use `send`.
1053
- * @param params - Send parameters
1054
- */
1055
- async sendBitcoin(params) {
1056
- if (params.amount <= 0) {
1057
- throw new Error("Amount must be positive");
1058
- }
1059
- if (!(0, arkTransaction_1.isValidArkAddress)(params.address)) {
1060
- throw new Error("Invalid Arkade address " + params.address);
1061
- }
1062
- if (params.selectedVtxos && params.selectedVtxos.length > 0) {
1063
- return this._withTxLock(async () => {
1064
- // Snapshot the active receive tapscript synchronously
1065
- // before any `await` so the change output's pkScript and
1066
- // the change-VTXO metadata written later by
1067
- // `updateDbAfterOffchainTx` are bound to the same
1068
- // tapscript even if `WalletReceiveRotator.rotate` fires
1069
- // during the offchain round-trip.
1070
- const offchainTapscript = this.offchainTapscript;
1071
- const arkAddress = offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
1072
- const selectedVtxoSum = params
1073
- .selectedVtxos.map((v) => v.value)
1074
- .reduce((a, b) => a + b, 0);
1075
- if (selectedVtxoSum < params.amount) {
1076
- throw new Error("Selected VTXOs do not cover specified amount");
1077
- }
1078
- const changeAmount = selectedVtxoSum - params.amount;
1079
- const selected = {
1080
- inputs: params.selectedVtxos,
1081
- changeAmount: BigInt(changeAmount),
1082
- };
1083
- const outputAddress = address_1.ArkAddress.decode(params.address);
1084
- const outputScript = BigInt(params.amount) < this.dustAmount
1085
- ? outputAddress.subdustPkScript
1086
- : outputAddress.pkScript;
1087
- const outputs = [
1088
- {
1089
- script: outputScript,
1090
- amount: BigInt(params.amount),
1091
- },
1092
- ];
1093
- // add change output if needed
1094
- if (selected.changeAmount > 0n) {
1095
- const changeOutputScript = selected.changeAmount < this.dustAmount
1096
- ? arkAddress.subdustPkScript
1097
- : arkAddress.pkScript;
1098
- outputs.push({
1099
- script: changeOutputScript,
1100
- amount: BigInt(selected.changeAmount),
1101
- });
1102
- }
1103
- this._addPendingSpends(selected.inputs);
1104
- try {
1105
- const { arkTxid, signedCheckpointTxs } = await this.buildAndSubmitOffchainTx(selected.inputs, outputs);
1106
- await this.updateDbAfterOffchainTx(selected.inputs, arkTxid, signedCheckpointTxs, params.amount, selected.changeAmount, selected.changeAmount > 0n ? outputs.length - 1 : 0, offchainTapscript);
1107
- return arkTxid;
1108
- }
1109
- finally {
1110
- this._removePendingSpends(selected.inputs);
1111
- }
1112
- });
1113
- }
1114
- return this.send({
1115
- address: params.address,
1116
- amount: params.amount,
1117
- });
1118
- }
1119
- /**
1120
- * Settle boarding inputs and/or virtual outputs into a finalized mainnet transaction.
1121
- *
1122
- * @param params - Optional settlement inputs and outputs. When omitted, the wallet settles all eligible funds.
1123
- * @param eventCallback - Optional callback invoked for settlement stream events.
1124
- * @returns The finalized Arkade transaction id
1125
- */
1126
- async settle(params, eventCallback) {
1127
- return this._withTxLock(() => this._settleImpl(params, eventCallback));
1128
- }
1129
- async _settleImpl(params, eventCallback) {
1130
- if (params?.inputs) {
1131
- for (const input of params.inputs) {
1132
- // validate arknotes inputs
1133
- if (typeof input === "string") {
1134
- try {
1135
- arknote_1.ArkNote.fromString(input);
1136
- }
1137
- catch (e) {
1138
- throw new Error(`Invalid arknote "${input}"`);
1139
- }
1140
- }
1141
- }
1142
- }
1143
- // if no params are provided, use all non-expired boarding inputs and offchain virtual outputs as inputs
1144
- // and send all to the offchain address
1145
- if (!params) {
1146
- const { fees } = await this.arkProvider.getInfo();
1147
- const estimator = new arkfee_1.Estimator(fees.intentFee);
1148
- let amount = 0;
1149
- const exitScript = tapscript_1.CSVMultisigTapscript.decode(base_1.hex.decode(this.boardingTapscript.exitScript));
1150
- const boardingTimelock = exitScript.params.timelock;
1151
- // For block-based timelocks, fetch the chain tip height
1152
- let chainTipHeight;
1153
- if (boardingTimelock.type === "blocks") {
1154
- const tip = await this.onchainProvider.getChainTip();
1155
- chainTipHeight = tip.height;
1156
- }
1157
- const boardingUtxos = (await this.getBoardingUtxos()).filter((utxo) => utxo.status.confirmed &&
1158
- !(0, arkTransaction_1.hasBoardingTxExpired)(utxo, boardingTimelock, chainTipHeight));
1159
- const filteredBoardingUtxos = [];
1160
- for (const utxo of boardingUtxos) {
1161
- const inputFee = estimator.evalOnchainInput({
1162
- amount: BigInt(utxo.value),
1163
- });
1164
- if (inputFee.value >= utxo.value) {
1165
- // skip if fees are greater than the boarding input value
1166
- continue;
1167
- }
1168
- filteredBoardingUtxos.push(utxo);
1169
- amount += utxo.value - inputFee.satoshis;
1170
- }
1171
- const vtxos = await this.getVtxos({ withRecoverable: true });
1172
- const filteredVtxos = [];
1173
- for (const vtxo of vtxos) {
1174
- const inputFee = estimator.evalOffchainInput({
1175
- amount: BigInt(vtxo.value),
1176
- type: vtxo.virtualStatus.state === "swept"
1177
- ? "recoverable"
1178
- : "vtxo",
1179
- weight: 0,
1180
- birth: vtxo.createdAt,
1181
- expiry: vtxo.virtualStatus.batchExpiry
1182
- ? new Date(vtxo.virtualStatus.batchExpiry)
1183
- : undefined,
1184
- });
1185
- if (inputFee.satoshis >= vtxo.value) {
1186
- // skip if fees are greater than the virtual output value
1187
- continue;
1188
- }
1189
- filteredVtxos.push(vtxo);
1190
- amount += vtxo.value - inputFee.satoshis;
1191
- }
1192
- const inputs = [...filteredBoardingUtxos, ...filteredVtxos];
1193
- if (inputs.length === 0) {
1194
- throw new Error("No inputs found");
1195
- }
1196
- const output = {
1197
- address: await this.getAddress(),
1198
- amount: BigInt(amount),
1199
- };
1200
- const outputFee = estimator.evalOffchainOutput({
1201
- amount: output.amount,
1202
- script: base_1.hex.encode(address_1.ArkAddress.decode(output.address).pkScript),
1203
- });
1204
- output.amount -= BigInt(outputFee.satoshis);
1205
- if (output.amount <= this.dustAmount) {
1206
- throw new Error("Output amount is below dust limit");
1207
- }
1208
- params = {
1209
- inputs,
1210
- outputs: [output],
1211
- };
1212
- }
1213
- const onchainOutputIndexes = [];
1214
- const outputs = [];
1215
- let hasOffchainOutputs = false;
1216
- for (const [index, output] of params.outputs.entries()) {
1217
- let script;
1218
- try {
1219
- // offchain
1220
- const addr = address_1.ArkAddress.decode(output.address);
1221
- script = addr.pkScript;
1222
- hasOffchainOutputs = true;
1223
- }
1224
- catch {
1225
- // onchain
1226
- const addr = (0, btc_signer_1.Address)(this.network).decode(output.address);
1227
- script = btc_signer_1.OutScript.encode(addr);
1228
- onchainOutputIndexes.push(index);
1229
- }
1230
- outputs.push({
1231
- amount: output.amount,
1232
- script,
1233
- });
1234
- }
1235
- // if some of the inputs hold assets, build the asset packet and append as output
1236
- // in the intent proof tx, there is a "fake" input at index 0
1237
- // so the real coin indices are offset by +1
1238
- const assetInputs = new Map();
1239
- for (let i = 0; i < params.inputs.length; i++) {
1240
- if ("assets" in params.inputs[i]) {
1241
- const assets = params.inputs[i]
1242
- .assets;
1243
- if (assets && assets.length > 0) {
1244
- assetInputs.set(i + 1, assets);
1245
- }
1246
- }
1247
- }
1248
- let outputAssets;
1249
- const destinationScript = address_1.ArkAddress.decode(await this.getAddress()).pkScript;
1250
- const assetOutputIndex = (0, delegator_1.findDestinationOutputIndex)(outputs, destinationScript);
1251
- if (assetInputs.size > 0) {
1252
- if (assetOutputIndex === -1) {
1253
- throw new Error("Cannot assign assets: no output matches the destination address");
1254
- }
1255
- // collect all input assets and assign them to the destination output
1256
- const allAssets = new Map();
1257
- for (const [, assets] of assetInputs) {
1258
- for (const asset of assets) {
1259
- const existing = allAssets.get(asset.assetId) ?? 0n;
1260
- allAssets.set(asset.assetId, existing + asset.amount);
1261
- }
1262
- }
1263
- outputAssets = [];
1264
- for (const [assetId, amount] of allAssets) {
1265
- outputAssets.push({ assetId, amount });
1266
- }
1267
- }
1268
- const recipients = params.outputs.map((output, i) => ({
1269
- address: output.address,
1270
- amount: Number(output.amount),
1271
- assets: i === assetOutputIndex ? outputAssets : undefined,
1272
- }));
1273
- if (outputAssets && outputAssets.length > 0) {
1274
- const assetPacket = (0, asset_1.createAssetPacket)(assetInputs, recipients);
1275
- outputs.push(extension_1.Extension.create([assetPacket]).txOut());
1276
- }
1277
- // session holds the state of the musig2 signing process of the virtual output tree
1278
- let session;
1279
- const signingPublicKeys = [];
1280
- if (hasOffchainOutputs) {
1281
- session = this.identity.signerSession();
1282
- signingPublicKeys.push(base_1.hex.encode(await session.getPublicKey()));
1283
- }
1284
- const [intent, deleteIntent] = await Promise.all([
1285
- this.makeRegisterIntentSignature(params.inputs, outputs, onchainOutputIndexes, signingPublicKeys),
1286
- this.makeDeleteIntentSignature(params.inputs),
1287
- ]);
1288
- const topics = [
1289
- ...signingPublicKeys,
1290
- ...params.inputs.map((input) => `${input.txid}:${input.vout}`),
1291
- ];
1292
- const abortController = new AbortController();
1293
- let stream;
1294
- // Optimistically hide these inputs from concurrent getVtxos() callers
1295
- // while the settlement is in flight. Set before safeRegisterIntent so
1296
- // there's no window between intent registration and coin-visibility.
1297
- this._addPendingSpends(params.inputs);
1298
- try {
1299
- stream = this.arkProvider.getEventStream(abortController.signal, topics);
1300
- // Prime the iterator so the provider opens the SSE subscription
1301
- // before safeRegisterIntent can trigger server-side batch events.
1302
- const firstNext = stream.next();
1303
- // If settle exits before Batch.join consumes the primed result,
1304
- // keep the orphaned promise from surfacing as an unhandled rejection.
1305
- void firstNext.catch(() => { });
1306
- const primedStream = (async function* () {
1307
- const first = await firstNext;
1308
- if (!first.done) {
1309
- yield first.value;
1310
- }
1311
- yield* stream;
1312
- })();
1313
- const intentId = await this.safeRegisterIntent(intent, params.inputs);
1314
- const handler = this.createBatchHandler(intentId, params.inputs, recipients, session);
1315
- const commitmentTxid = await batch_1.Batch.join(primedStream, handler, {
1316
- abortController,
1317
- skipVtxoTreeSigning: !hasOffchainOutputs,
1318
- eventCallback: eventCallback
1319
- ? (event) => Promise.resolve(eventCallback(event))
1320
- : undefined,
1321
- });
1322
- await this.updateDbAfterSettle(params.inputs, commitmentTxid);
1323
- return commitmentTxid;
1324
- }
1325
- catch (error) {
1326
- // delete the intent to not be stuck in the queue. If deletion fails
1327
- // the intent stays on the server and the next settle will hit
1328
- // "duplicated input" in safeRegisterIntent — surface the failure
1329
- // rather than silently swallowing it.
1330
- const inputIds = params.inputs
1331
- .map((i) => `${i.txid}:${i.vout}`)
1332
- .join(",");
1333
- await this.arkProvider.deleteIntent(deleteIntent).catch((e) => {
1334
- console.warn(`Failed to delete intent after settle failure for inputs [${inputIds}]; intent may linger on server and cause 'duplicated input' on next settle`, e);
1335
- });
1336
- throw error;
1337
- }
1338
- finally {
1339
- // Clear state first so a synchronous handler firing from abort()
1340
- // never observes a stale pending-spend set.
1341
- this._removePendingSpends(params.inputs);
1342
- // close the stream — abort() fires the in-body handler if the
1343
- // generator has started iterating; return() also releases the
1344
- // eager resource if the body is still suspended or never ran
1345
- // (e.g. safeRegisterIntent threw before Batch.join was called).
1346
- abortController.abort();
1347
- await stream?.return?.().catch(() => { });
1348
- }
1349
- }
1350
- async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
1351
- // the signed forfeits transactions to submit
1352
- const signedForfeits = [];
1353
- const isVtxo = (input) => "virtualStatus" in input;
1354
- let settlementPsbt = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(event.commitmentTx));
1355
- let hasBoardingUtxos = false;
1356
- let connectorIndex = 0;
1357
- const connectorsLeaves = connectorsGraph?.leaves() || [];
1358
- for (const input of inputs) {
1359
- // boarding input, we need to sign the settlement tx
1360
- if (!isVtxo(input)) {
1361
- for (let i = 0; i < settlementPsbt.inputsLength; i++) {
1362
- const settlementInput = settlementPsbt.getInput(i);
1363
- if (!settlementInput.txid ||
1364
- settlementInput.index === undefined) {
1365
- throw new Error("The server returned incomplete data. No settlement input found in the PSBT");
1366
- }
1367
- const inputTxId = base_1.hex.encode(settlementInput.txid);
1368
- if (inputTxId !== input.txid)
1369
- continue;
1370
- if (settlementInput.index !== input.vout)
1371
- continue;
1372
- // input found in the settlement tx, sign it
1373
- settlementPsbt.updateInput(i, {
1374
- tapLeafScript: [input.forfeitTapLeafScript],
1375
- });
1376
- const script = settlementPsbt.getInput(i).witnessUtxo?.script;
1377
- if (!script) {
1378
- throw new Error("The server returned incomplete data. Settlement input is missing witnessUtxo.script");
1379
- }
1380
- settlementPsbt = await this._signerRouter.sign(settlementPsbt, [{ index: i, lookupScript: script }]);
1381
- hasBoardingUtxos = true;
1382
- break;
1383
- }
1384
- continue;
1385
- }
1386
- if ((0, _1.isRecoverable)(input) || (0, _1.isSubdust)(input, this.dustAmount)) {
1387
- // recoverable or subdust coin, we don't need to create a forfeit tx
1388
- continue;
1389
- }
1390
- if (connectorsLeaves.length === 0) {
1391
- throw new Error("connectors not received");
1392
- }
1393
- if (connectorIndex >= connectorsLeaves.length) {
1394
- throw new Error("not enough connectors received");
1395
- }
1396
- const connectorLeaf = connectorsLeaves[connectorIndex];
1397
- const connectorTxId = connectorLeaf.id;
1398
- const connectorOutput = connectorLeaf.getOutput(0);
1399
- if (!connectorOutput) {
1400
- throw new Error("connector output not found");
1401
- }
1402
- const connectorAmount = connectorOutput.amount;
1403
- const connectorPkScript = connectorOutput.script;
1404
- if (!connectorAmount || !connectorPkScript) {
1405
- throw new Error("invalid connector output");
1406
- }
1407
- connectorIndex++;
1408
- let forfeitTx = (0, forfeit_1.buildForfeitTx)([
1409
- {
1410
- txid: input.txid,
1411
- index: input.vout,
1412
- witnessUtxo: {
1413
- amount: BigInt(input.value),
1414
- script: base_2.VtxoScript.decode(input.tapTree).pkScript,
1415
- },
1416
- sighashType: btc_signer_1.SigHash.DEFAULT,
1417
- tapLeafScript: [input.forfeitTapLeafScript],
1418
- },
1419
- {
1420
- txid: connectorTxId,
1421
- index: 0,
1422
- witnessUtxo: {
1423
- amount: connectorAmount,
1424
- script: connectorPkScript,
1425
- },
1426
- },
1427
- ], forfeitOutputScript);
1428
- // do not sign the connector input
1429
- forfeitTx = await this._signerRouter.sign(forfeitTx, [
1430
- {
1431
- index: 0,
1432
- lookupScript: base_2.VtxoScript.decode(input.tapTree).pkScript,
1433
- },
1434
- ]);
1435
- signedForfeits.push(base_1.base64.encode(forfeitTx.toPSBT()));
1436
- }
1437
- if (signedForfeits.length > 0 || hasBoardingUtxos) {
1438
- await this.arkProvider.submitSignedForfeitTxs(signedForfeits, hasBoardingUtxos
1439
- ? base_1.base64.encode(settlementPsbt.toPSBT())
1440
- : undefined);
1441
- }
1442
- }
1443
- /**
1444
- * Create a batch event handler for settlement flows.
1445
- *
1446
- * @param intentId - The intent ID.
1447
- * @param inputs - Inputs used by the intent.
1448
- * @param expectedRecipients - Expected recipients to validate in the virtual output tree.
1449
- * @param session - Optional musig2 signing session. When omitted, signing steps are skipped.
1450
- */
1451
- createBatchHandler(intentId, inputs, expectedRecipients, session) {
1452
- let sweepTapTreeRoot;
1453
- return {
1454
- onBatchStarted: async (event) => {
1455
- const utf8IntentId = new TextEncoder().encode(intentId);
1456
- const intentIdHash = (0, utils_js_1.sha256)(utf8IntentId);
1457
- const intentIdHashStr = base_1.hex.encode(intentIdHash);
1458
- let skip = true;
1459
- // check if our intent ID hash matches any in the event
1460
- for (const idHash of event.intentIdHashes) {
1461
- if (idHash === intentIdHashStr) {
1462
- if (!this.arkProvider) {
1463
- throw new Error("Arkade provider not configured");
1464
- }
1465
- await this.arkProvider.confirmRegistration(intentId);
1466
- skip = false;
1467
- }
1468
- }
1469
- if (skip) {
1470
- return { skip };
1471
- }
1472
- const sweepTapscript = tapscript_1.CSVMultisigTapscript.encode({
1473
- timelock: {
1474
- value: event.batchExpiry,
1475
- type: event.batchExpiry >= 512n ? "seconds" : "blocks",
1476
- },
1477
- pubkeys: [this.forfeitPubkey],
1478
- }).script;
1479
- sweepTapTreeRoot = (0, payment_js_1.tapLeafHash)(sweepTapscript);
1480
- return { skip: false };
1481
- },
1482
- onTreeSigningStarted: async (event, vtxoTree) => {
1483
- if (!session) {
1484
- return { skip: true };
1485
- }
1486
- if (!sweepTapTreeRoot) {
1487
- throw new Error("Sweep tap tree root not set");
1488
- }
1489
- const xOnlyPublicKeys = event.cosignersPublicKeys.map((k) => k.slice(2));
1490
- const signerPublicKey = await session.getPublicKey();
1491
- const xonlySignerPublicKey = signerPublicKey.subarray(1);
1492
- if (!xOnlyPublicKeys.includes(base_1.hex.encode(xonlySignerPublicKey))) {
1493
- // not a cosigner, skip the signing
1494
- return { skip: true };
1495
- }
1496
- // validate the unsigned virtual output tree
1497
- const commitmentTx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(event.unsignedCommitmentTx));
1498
- (0, validation_1.validateVtxoTxGraph)(vtxoTree, commitmentTx, sweepTapTreeRoot);
1499
- // validate that all expected receivers are in the virtual output tree with correct amounts and assets
1500
- if (expectedRecipients && expectedRecipients.length > 0) {
1501
- (0, validation_2.validateBatchRecipients)(commitmentTx, vtxoTree.leaves(), expectedRecipients, this.network);
1502
- }
1503
- const sharedOutput = commitmentTx.getOutput(0);
1504
- if (!sharedOutput?.amount) {
1505
- throw new Error("Shared output not found");
1506
- }
1507
- await session.init(vtxoTree, sweepTapTreeRoot, sharedOutput.amount);
1508
- const pubkey = base_1.hex.encode(await session.getPublicKey());
1509
- const nonces = await session.getNonces();
1510
- await this.arkProvider.submitTreeNonces(event.id, pubkey, nonces);
1511
- return { skip: false };
1512
- },
1513
- onTreeNonces: async (event) => {
1514
- if (!session) {
1515
- return { fullySigned: true }; // Signing complete (no signing needed)
1516
- }
1517
- const { hasAllNonces } = await session.aggregatedNonces(event.txid, event.nonces);
1518
- // wait to receive and aggregate all nonces before sending signatures
1519
- if (!hasAllNonces)
1520
- return { fullySigned: false };
1521
- const signatures = await session.sign();
1522
- const pubkey = base_1.hex.encode(await session.getPublicKey());
1523
- await this.arkProvider.submitTreeSignatures(event.id, pubkey, signatures);
1524
- return { fullySigned: true };
1525
- },
1526
- onBatchFinalization: async (event, _, connectorTree) => {
1527
- if (!this.forfeitOutputScript) {
1528
- throw new Error("Forfeit output script not set");
1529
- }
1530
- if (connectorTree) {
1531
- (0, validation_1.validateConnectorsTxGraph)(event.commitmentTx, connectorTree);
1532
- }
1533
- await this.handleSettlementFinalizationEvent(event, inputs, this.forfeitOutputScript, connectorTree);
1534
- },
1535
- };
1536
- }
1537
- /**
1538
- * Build {@link InputSigningJob}s for a tx whose signable inputs can be
1539
- * resolved from their own `witnessUtxo.script`. Inputs without a
1540
- * `witnessUtxo` are silently omitted, mirroring the wallet's
1541
- * historical silent-skip behaviour for cosigner/connector inputs.
1542
- */
1543
- inputSigningJobsFromWitnessUtxos(tx, indexes) {
1544
- const candidateIndexes = indexes ?? Array.from({ length: tx.inputsLength }, (_, i) => i);
1545
- const jobs = [];
1546
- for (const index of candidateIndexes) {
1547
- const script = tx.getInput(index).witnessUtxo?.script;
1548
- if (script)
1549
- jobs.push({ index, lookupScript: script });
1550
- }
1551
- return jobs;
1552
- }
1553
- async safeRegisterIntent(intent, inputs) {
1554
- try {
1555
- return await this.arkProvider.registerIntent(intent);
1556
- }
1557
- catch (error) {
1558
- // catch the "already registered by another intent" error
1559
- if (error instanceof errors_1.ArkError &&
1560
- error.code === 0 &&
1561
- error.message.includes("duplicated input")) {
1562
- // Clear any queued intent spending these exact inputs. The
1563
- // previous implementation signed a proof over getVtxos() only,
1564
- // which misses boarding UTXOs — the most common trigger for
1565
- // "duplicated input" on the auto-settle path. Signing the
1566
- // caller's own inputs keeps the proof surgical and correct
1567
- // regardless of whether the stuck input is a VTXO or boarding.
1568
- const deleteIntent = await this.makeDeleteIntentSignature(inputs);
1569
- await this.arkProvider.deleteIntent(deleteIntent);
1570
- // try again
1571
- return this.arkProvider.registerIntent(intent);
1572
- }
1573
- throw error;
1574
- }
1575
- }
1576
- async makeRegisterIntentSignature(coins, outputs, onchainOutputsIndexes, cosignerPubKeys, validAt) {
1577
- const message = {
1578
- type: "register",
1579
- onchain_output_indexes: onchainOutputsIndexes,
1580
- valid_at: validAt ? Math.floor(validAt) : 0,
1581
- expire_at: 0,
1582
- cosigners_public_keys: cosignerPubKeys,
1583
- };
1584
- const proof = intent_1.Intent.create(message, coins, outputs);
1585
- const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
1586
- return {
1587
- proof: base_1.base64.encode(signedProof.toPSBT()),
1588
- message,
1589
- };
1590
- }
1591
- async makeDeleteIntentSignature(coins) {
1592
- const message = {
1593
- type: "delete",
1594
- expire_at: 0,
1595
- };
1596
- const proof = intent_1.Intent.create(message, coins, []);
1597
- const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
1598
- return {
1599
- proof: base_1.base64.encode(signedProof.toPSBT()),
1600
- message,
1601
- };
1602
- }
1603
- async makeGetPendingTxIntentSignature(coins) {
1604
- const message = {
1605
- type: "get-pending-tx",
1606
- expire_at: 0,
1607
- };
1608
- const proof = intent_1.Intent.create(message, coins, []);
1609
- const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
1610
- return {
1611
- proof: base_1.base64.encode(signedProof.toPSBT()),
1612
- message,
1613
- };
1614
- }
1615
- /**
1616
- * Finalizes pending transactions by retrieving them from the server and finalizing each one.
1617
- * Skips the server check entirely when no send was interrupted (no pending tx flag set).
1618
- * @param vtxos - Optional list of virtual outputs to use instead of retrieving them from the server
1619
- * @returns Array of transaction IDs that were finalized
1620
- */
1621
- async finalizePendingTxs(vtxos) {
1622
- const hasPending = await this.hasPendingTxFlag();
1623
- if (!hasPending) {
1624
- return { finalized: [], pending: [] };
1625
- }
1626
- const MAX_INPUTS_PER_INTENT = 20;
1627
- if (!vtxos || vtxos.length === 0) {
1628
- // Batch all scripts into a single indexer call
1629
- const scriptMap = await this.getScriptMap();
1630
- const allExtended = [];
1631
- const allScripts = [...scriptMap.keys()];
1632
- const { vtxos: fetchedVtxos } = await this.indexerProvider.getVtxos({
1633
- scripts: allScripts,
1634
- });
1635
- for (const vtxo of fetchedVtxos) {
1636
- const vtxoScript = scriptMap.get(vtxo.script);
1637
- if (!vtxoScript)
1638
- continue;
1639
- if (vtxo.virtualStatus.state === "swept" ||
1640
- vtxo.virtualStatus.state === "settled") {
1641
- continue;
1642
- }
1643
- allExtended.push({
1644
- ...vtxo,
1645
- forfeitTapLeafScript: vtxoScript.forfeit(),
1646
- intentTapLeafScript: vtxoScript.forfeit(),
1647
- tapTree: vtxoScript.encode(),
1648
- });
1649
- }
1650
- if (allExtended.length === 0) {
1651
- return { finalized: [], pending: [] };
1652
- }
1653
- vtxos = allExtended;
1654
- }
1655
- const batches = [];
1656
- for (let i = 0; i < vtxos.length; i += MAX_INPUTS_PER_INTENT) {
1657
- batches.push(vtxos.slice(i, i + MAX_INPUTS_PER_INTENT));
1658
- }
1659
- // Track seen arkTxids so parallel batches don't finalize the same tx twice
1660
- const seen = new Set();
1661
- const results = await Promise.all(batches.map(async (batch) => {
1662
- const batchFinalized = [];
1663
- const batchPending = [];
1664
- const intent = await this.makeGetPendingTxIntentSignature(batch);
1665
- const pendingTxs = await this.arkProvider.getPendingTxs(intent);
1666
- for (const pendingTx of pendingTxs) {
1667
- if (seen.has(pendingTx.arkTxid))
1668
- continue;
1669
- seen.add(pendingTx.arkTxid);
1670
- batchPending.push(pendingTx.arkTxid);
1671
- try {
1672
- const finalCheckpoints = await Promise.all(pendingTx.signedCheckpointTxs.map(async (c) => {
1673
- const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(c));
1674
- const signedCheckpoint = await this._signerRouter.sign(tx, this.inputSigningJobsFromWitnessUtxos(tx));
1675
- return base_1.base64.encode(signedCheckpoint.toPSBT());
1676
- }));
1677
- await this.arkProvider.finalizeTx(pendingTx.arkTxid, finalCheckpoints);
1678
- batchFinalized.push(pendingTx.arkTxid);
1679
- }
1680
- catch (error) {
1681
- console.error(`Failed to finalize transaction ${pendingTx.arkTxid}:`, error);
1682
- }
1683
- }
1684
- return {
1685
- finalized: batchFinalized,
1686
- pending: batchPending,
1687
- };
1688
- }));
1689
- const finalized = [];
1690
- const pending = [];
1691
- for (const result of results) {
1692
- finalized.push(...result.finalized);
1693
- pending.push(...result.pending);
1694
- }
1695
- // Only clear the flag if every discovered pending tx was finalized;
1696
- // if any failed, keep it so recovery retries on next startup.
1697
- if (finalized.length === pending.length) {
1698
- await this.setPendingTxFlag(false);
1699
- }
1700
- return { finalized, pending };
1701
- }
1702
- async hasPendingTxFlag() {
1703
- const state = await this.walletRepository.getWalletState();
1704
- return state?.settings?.hasPendingTx === true;
1705
- }
1706
- async setPendingTxFlag(value) {
1707
- await (0, syncCursors_1.updateWalletState)(this.walletRepository, (state) => ({
1708
- ...state,
1709
- settings: { ...state.settings, hasPendingTx: value },
1710
- }));
1711
- }
1712
- /**
1713
- * Send BTC and/or assets to one or more recipients.
1714
- *
1715
- * @param args - Recipients with their addresses, BTC amounts, and assets
1716
- * @returns Promise resolving to the Arkade transaction ID
1717
- *
1718
- * @example
1719
- * ```typescript
1720
- * const txid = await wallet.send({
1721
- * address: 'ark1q...',
1722
- * amount: 1000, // (optional, default to dust) btc amount to send to the output
1723
- * assets: [{ assetId: 'abc123...', amount: 50n }] // (optional) list of assets to send
1724
- * });
1725
- * ```
1726
- */
1727
- async send(...args) {
1728
- return this._withTxLock(() => this._sendImpl(...args));
1729
- }
1730
- async _sendImpl(...args) {
1731
- if (args.length === 0) {
1732
- throw new Error("At least one receiver is required");
1733
- }
1734
- // Snapshot the active receive tapscript synchronously before any
1735
- // `await`. `WalletReceiveRotator.rotate` mutates
1736
- // `this.offchainTapscript` without acquiring `_txLock`, so any
1737
- // yield between here and `updateDbAfterOffchainTx` opens a window
1738
- // where the change-output pkScript (built from `outputAddress`
1739
- // below) and the change-VTXO metadata (built from the snapshot
1740
- // inside `updateDbAfterOffchainTx`) could come from different
1741
- // tapscripts. Threading the snapshot pins both reads.
1742
- const offchainTapscript = this.offchainTapscript;
1743
- const outputAddress = offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
1744
- const address = outputAddress.encode();
1745
- // validate recipients and populate undefined amount with dust amount
1746
- const recipients = (0, utils_1.validateRecipients)(args, Number(this.dustAmount));
1747
- const virtualCoins = await this.getVtxos({
1748
- withRecoverable: false,
1749
- });
1750
- // keep track of asset changes
1751
- const assetChanges = new Map();
1752
- let selectedCoins = [];
1753
- let btcAmountToSelect = 0;
1754
- for (const recipient of recipients) {
1755
- btcAmountToSelect += Math.max(recipient.amount, Number(this.dustAmount));
1756
- }
1757
- // select assets
1758
- for (const recipient of recipients) {
1759
- if (!recipient.assets) {
1760
- continue;
1761
- }
1762
- for (const receiverAsset of recipient.assets) {
1763
- let amountToSelect = receiverAsset.amount;
1764
- // check if existing change covers the needed amount
1765
- const existingChange = assetChanges.get(receiverAsset.assetId) ?? 0n;
1766
- if (existingChange >= amountToSelect) {
1767
- assetChanges.set(receiverAsset.assetId, existingChange - amountToSelect);
1768
- if (assetChanges.get(receiverAsset.assetId) === 0n) {
1769
- assetChanges.delete(receiverAsset.assetId);
1770
- }
1771
- continue;
1772
- }
1773
- if (existingChange > 0n) {
1774
- amountToSelect -= existingChange;
1775
- assetChanges.delete(receiverAsset.assetId);
1776
- }
1777
- const availableCoins = virtualCoins.filter((c) => !selectedCoins.find((sc) => sc.txid === c.txid && sc.vout === c.vout));
1778
- const { selected, totalAssetAmount } = (0, asset_1.selectCoinsWithAsset)(availableCoins, receiverAsset.assetId, amountToSelect);
1779
- for (const coin of selected) {
1780
- selectedCoins.push(coin);
1781
- // asset coins contain btc, subtract from total amount to select
1782
- btcAmountToSelect -= coin.value;
1783
- // coin may contain other assets, add them to asset changes
1784
- if (coin.assets) {
1785
- for (const a of coin.assets) {
1786
- if (a.assetId === receiverAsset.assetId) {
1787
- continue;
1788
- }
1789
- const existing = assetChanges.get(a.assetId) ?? 0n;
1790
- assetChanges.set(a.assetId, existing + a.amount);
1791
- }
1792
- }
1793
- }
1794
- const assetChangeAmount = totalAssetAmount - amountToSelect;
1795
- if (assetChangeAmount > 0n) {
1796
- const existing = assetChanges.get(receiverAsset.assetId) ?? 0n;
1797
- assetChanges.set(receiverAsset.assetId, existing + assetChangeAmount);
1798
- }
1799
- }
1800
- }
1801
- // select remaining btc
1802
- if (btcAmountToSelect > 0) {
1803
- const availableCoins = virtualCoins.filter((c) => !selectedCoins.find((sc) => sc.txid === c.txid && sc.vout === c.vout));
1804
- const { inputs: btcCoins } = selectVirtualCoins(availableCoins, btcAmountToSelect);
1805
- // some coins may contain assets, add them to asset changes
1806
- for (const coin of btcCoins) {
1807
- if (coin.assets) {
1808
- for (const asset of coin.assets) {
1809
- const existing = assetChanges.get(asset.assetId) ?? 0n;
1810
- assetChanges.set(asset.assetId, existing + asset.amount);
1811
- }
1812
- }
1813
- }
1814
- selectedCoins = [...selectedCoins, ...btcCoins];
1815
- }
1816
- let totalBtcSelected = selectedCoins.reduce((sum, c) => sum + c.value, 0);
1817
- // build tx outputs
1818
- const outputs = recipients.map((recipient) => ({
1819
- script: recipient.script,
1820
- amount: BigInt(recipient.amount),
1821
- }));
1822
- const totalBtcOutput = outputs.reduce((sum, o) => sum + Number(o.amount), 0);
1823
- let changeAmount = totalBtcSelected - totalBtcOutput;
1824
- // enforce minimum change amount when there are asset changes
1825
- if (assetChanges.size > 0 && changeAmount < Number(this.dustAmount)) {
1826
- const availableCoins = virtualCoins.filter((c) => !selectedCoins.find((sc) => sc.txid === c.txid && sc.vout === c.vout));
1827
- const { inputs: extraCoins } = selectVirtualCoins(availableCoins, Number(this.dustAmount) - changeAmount);
1828
- for (const coin of extraCoins) {
1829
- if (coin.assets) {
1830
- for (const asset of coin.assets) {
1831
- const existing = assetChanges.get(asset.assetId) ?? 0n;
1832
- assetChanges.set(asset.assetId, existing + asset.amount);
1833
- }
1834
- }
1835
- }
1836
- selectedCoins = [...selectedCoins, ...extraCoins];
1837
- totalBtcSelected += extraCoins.reduce((sum, c) => sum + c.value, 0);
1838
- changeAmount = totalBtcSelected - totalBtcOutput;
1839
- }
1840
- // build change receiver with BTC change and all asset changes
1841
- let changeReceiver;
1842
- let changeIndex = 0;
1843
- if (changeAmount > 0) {
1844
- const changeAssets = [];
1845
- for (const [assetId, amount] of assetChanges) {
1846
- if (amount > 0n) {
1847
- changeAssets.push({ assetId, amount });
1848
- }
1849
- }
1850
- changeIndex = outputs.length;
1851
- outputs.push({
1852
- script: BigInt(changeAmount) < this.dustAmount
1853
- ? outputAddress.subdustPkScript
1854
- : outputAddress.pkScript,
1855
- amount: BigInt(changeAmount),
1856
- });
1857
- changeReceiver = {
1858
- address: address,
1859
- amount: changeAmount,
1860
- assets: changeAssets.length > 0 ? changeAssets : undefined,
1861
- };
1862
- }
1863
- // create asset packet only if there are assets involved
1864
- const assetInputs = (0, asset_1.selectedCoinsToAssetInputs)(selectedCoins);
1865
- const hasAssets = assetInputs.size > 0 ||
1866
- recipients.some((r) => r.assets && r.assets.length > 0);
1867
- if (hasAssets) {
1868
- const assetPacket = (0, asset_1.createAssetPacket)(assetInputs, recipients, changeReceiver);
1869
- outputs.push(extension_1.Extension.create([assetPacket]).txOut());
1870
- }
1871
- const sentAmount = recipients.reduce((sum, r) => sum + r.amount, 0);
1872
- // Optimistically hide selected coins from concurrent getVtxos() while
1873
- // the offchain tx is in flight.
1874
- this._addPendingSpends(selectedCoins);
1875
- try {
1876
- const { arkTxid, signedCheckpointTxs } = await this.buildAndSubmitOffchainTx(selectedCoins, outputs);
1877
- await this.updateDbAfterOffchainTx(selectedCoins, arkTxid, signedCheckpointTxs, sentAmount, BigInt(changeAmount), changeReceiver ? changeIndex : 0, offchainTapscript, changeReceiver?.assets);
1878
- return arkTxid;
1879
- }
1880
- finally {
1881
- this._removePendingSpends(selectedCoins);
1882
- }
1883
- }
1884
- /**
1885
- * Build an offchain transaction from the given inputs and outputs,
1886
- * sign it, submit to the Arkade provider, and finalize.
1887
- * @returns The Arkade transaction id and server-signed checkpoint PSBTs (for bookkeeping)
1888
- */
1889
- async buildAndSubmitOffchainTx(inputs, outputs) {
1890
- const offchainTx = (0, arkTransaction_1.buildOffchainTx)(inputs.map((input) => {
1891
- return {
1892
- ...input,
1893
- tapLeafScript: input.forfeitTapLeafScript,
1894
- };
1895
- }), outputs, this.serverUnrollScript);
1896
- // arkTx inputs spend checkpoint outputs, so each input's
1897
- // `witnessUtxo.script` is the checkpoint pkScript — not the
1898
- // source VTXO contract's pkScript. Build the routing jobs from
1899
- // the source VTXO scripts (positionally aligned to `inputs[i]`)
1900
- // so the router can resolve each input's owning contract.
1901
- const arkTxJobs = inputs.map((input, index) => ({
1902
- index,
1903
- lookupScript: base_2.VtxoScript.decode(input.tapTree).pkScript,
1904
- }));
1905
- const signedVirtualTx = await this._signerRouter.sign(offchainTx.arkTx, arkTxJobs);
1906
- // Mark pending before submitting — if we crash between submit and
1907
- // finalize, the next init will recover via finalizePendingTxs.
1908
- await this.setPendingTxFlag(true);
1909
- const { arkTxid, signedCheckpointTxs } = await this.arkProvider.submitTx(base_1.base64.encode(signedVirtualTx.toPSBT()), offchainTx.checkpoints.map((c) => base_1.base64.encode(c.toPSBT())));
1910
- const finalCheckpoints = await Promise.all(signedCheckpointTxs.map(async (c) => {
1911
- const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(c));
1912
- const signedCheckpoint = await this._signerRouter.sign(tx, this.inputSigningJobsFromWitnessUtxos(tx));
1913
- return base_1.base64.encode(signedCheckpoint.toPSBT());
1914
- }));
1915
- await this.arkProvider.finalizeTx(arkTxid, finalCheckpoints);
1916
- try {
1917
- await this.setPendingTxFlag(false);
1918
- }
1919
- catch (error) {
1920
- console.error("Failed to clear pending tx flag:", error);
1921
- }
1922
- return { arkTxid, signedCheckpointTxs };
1923
- }
1924
- // mark virtual outputs as spent, save change outputs if any.
1925
- // `offchainTapscript` is the snapshot the caller captured under
1926
- // `_txLock` before any `await`; deriving both the change-VTXO
1927
- // metadata and `primaryAddress` from it here guarantees the local
1928
- // record matches the pkScript the server saw on the inbound
1929
- // transaction, even if `WalletReceiveRotator.rotate` swaps
1930
- // `this.offchainTapscript` mid-flight.
1931
- async updateDbAfterOffchainTx(inputs, arkTxid, signedCheckpointTxs, sentAmount, changeAmount, changeVout, offchainTapscript, changeAssets) {
1932
- const primaryAddress = offchainTapscript
1933
- .address(this.network.hrp, this.arkServerPublicKey)
1934
- .encode();
1935
- try {
1936
- const spentVtxos = [];
1937
- const commitmentTxIds = new Set();
1938
- let batchExpiry = Number.MAX_SAFE_INTEGER;
1939
- if (inputs.length !== signedCheckpointTxs.length) {
1940
- console.warn(`updateDbAfterOffchainTx: inputs length (${inputs.length}) differs from signedCheckpointTxs length (${signedCheckpointTxs.length})`);
1941
- }
1942
- const safeLength = Math.min(inputs.length, signedCheckpointTxs.length);
1943
- const cm = await this.getContractManager();
1944
- const annotatedInputs = await cm.annotateVtxos(inputs);
1945
- for (const [inputIndex, vtxo] of annotatedInputs.entries()) {
1946
- if (inputIndex < safeLength &&
1947
- signedCheckpointTxs[inputIndex]) {
1948
- const checkpoint = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(signedCheckpointTxs[inputIndex]));
1949
- spentVtxos.push({
1950
- ...vtxo,
1951
- virtualStatus: {
1952
- ...vtxo.virtualStatus,
1953
- state: "spent",
1954
- },
1955
- spentBy: checkpoint.id,
1956
- arkTxId: arkTxid,
1957
- isSpent: true,
1958
- });
1959
- }
1960
- else {
1961
- spentVtxos.push({
1962
- ...vtxo,
1963
- virtualStatus: {
1964
- ...vtxo.virtualStatus,
1965
- state: "spent",
1966
- },
1967
- arkTxId: arkTxid,
1968
- isSpent: true,
1969
- });
1970
- }
1971
- if (vtxo.virtualStatus.commitmentTxIds) {
1972
- for (const id of vtxo.virtualStatus.commitmentTxIds) {
1973
- commitmentTxIds.add(id);
1974
- }
1975
- }
1976
- if (vtxo.virtualStatus.batchExpiry) {
1977
- batchExpiry = Math.min(batchExpiry, vtxo.virtualStatus.batchExpiry);
1978
- }
1979
- }
1980
- const createdAt = Date.now();
1981
- // Only save a change virtual output for preconfirmed coins (those with a batchExpiry).
1982
- // Inputs without a batchExpiry are already settled/unrolled and don't need tracking.
1983
- let changeVtxo;
1984
- if (changeAmount > 0n && batchExpiry !== Number.MAX_SAFE_INTEGER) {
1985
- changeVtxo = {
1986
- txid: arkTxid,
1987
- vout: changeVout,
1988
- createdAt: new Date(createdAt),
1989
- forfeitTapLeafScript: offchainTapscript.forfeit(),
1990
- intentTapLeafScript: offchainTapscript.forfeit(),
1991
- isUnrolled: false,
1992
- isSpent: false,
1993
- tapTree: offchainTapscript.encode(),
1994
- value: Number(changeAmount),
1995
- virtualStatus: {
1996
- state: "preconfirmed",
1997
- commitmentTxIds: Array.from(commitmentTxIds),
1998
- batchExpiry,
1999
- },
2000
- status: {
2001
- confirmed: false,
2002
- },
2003
- assets: changeAssets,
2004
- script: base_1.hex.encode(offchainTapscript.pkScript),
2005
- };
2006
- }
2007
- // Route spent rows to their owning contract bucket. The wallet's
2008
- // primary contract is registered with the manager at boot, so
2009
- // `addrByScript` already includes it; in a multi-contract spend
2010
- // each input may belong to a different contract.
2011
- const contracts = await cm.getContracts();
2012
- const addrByScript = new Map(contracts.map((c) => [c.script, c.address]));
2013
- const spentByScript = new Map();
2014
- for (const v of spentVtxos) {
2015
- if (!v.script) {
2016
- throw new Error(`Wallet.updateDbAfterOffchainTx: spent VTXO ${v.txid}:${v.vout} has no script`);
2017
- }
2018
- const arr = spentByScript.get(v.script) ?? [];
2019
- arr.push(v);
2020
- spentByScript.set(v.script, arr);
2021
- }
2022
- for (const [script, vtxos] of spentByScript) {
2023
- // User-initiated send path: a wrong-script row here means the
2024
- // wallet is about to record ownership against the wrong
2025
- // contract — fail loudly rather than persist inconsistent state.
2026
- (0, vtxoOwnership_1.validateVtxosForScript)(vtxos, script, "Wallet.updateDbAfterOffchainTx");
2027
- const targetAddr = addrByScript.get(script);
2028
- if (!targetAddr) {
2029
- throw new Error(`Wallet.updateDbAfterOffchainTx: no contract owns script ${script}`);
2030
- }
2031
- await (0, vtxoOwnership_1.saveVtxosForContract)(this.walletRepository, { script, address: targetAddr }, vtxos);
2032
- }
2033
- // Change is always primary-script by construction.
2034
- if (changeVtxo) {
2035
- await (0, vtxoOwnership_1.saveVtxosForContract)(this.walletRepository, { script: changeVtxo.script, address: primaryAddress }, [changeVtxo]);
2036
- }
2037
- await this.walletRepository.saveTransactions(primaryAddress, [
2038
- {
2039
- key: {
2040
- boardingTxid: "",
2041
- commitmentTxid: "",
2042
- arkTxid: arkTxid,
2043
- },
2044
- amount: sentAmount,
2045
- type: _1.TxType.TxSent,
2046
- settled: false,
2047
- createdAt,
2048
- },
2049
- ]);
2050
- }
2051
- catch (e) {
2052
- console.warn("error saving offchain tx to repository", e);
2053
- throw e;
2054
- }
2055
- }
2056
- // mark virtual outputs as spent/settled, remove boarding inputs
2057
- async updateDbAfterSettle(inputs, commitmentTxid) {
2058
- try {
2059
- const boardingAddress = await this.getBoardingAddress();
2060
- const spentVtxos = [];
2061
- const inputArkTxIds = new Set();
2062
- const boardingUtxoToRemove = new Set();
2063
- const isVtxo = (input) => "virtualStatus" in input;
2064
- const vtxoInputs = inputs.filter(isVtxo);
2065
- const cm = await this.getContractManager();
2066
- const annotatedVtxos = await cm.annotateVtxos(vtxoInputs);
2067
- const annotatedByKey = new Map(annotatedVtxos.map((v) => [`${v.txid}:${v.vout}`, v]));
2068
- for (const input of inputs) {
2069
- if (isVtxo(input)) {
2070
- // virtual output = mark it settled
2071
- const vtxo = annotatedByKey.get(`${input.txid}:${input.vout}`);
2072
- if (vtxo.arkTxId) {
2073
- inputArkTxIds.add(vtxo.arkTxId);
2074
- }
2075
- spentVtxos.push({
2076
- ...vtxo,
2077
- virtualStatus: {
2078
- ...vtxo.virtualStatus,
2079
- state: "settled",
2080
- },
2081
- settledBy: commitmentTxid,
2082
- isSpent: true,
2083
- });
2084
- }
2085
- else {
2086
- // boarding input = remove it
2087
- boardingUtxoToRemove.add(`${input.txid}:${input.vout}`);
2088
- }
2089
- }
2090
- if (spentVtxos.length > 0) {
2091
- // Route settled rows to their owning contract bucket. In a
2092
- // multi-contract settle the inputs may belong to several
2093
- // contracts; the wallet's primary contract is registered with
2094
- // the manager at boot, so its address is in `addrByScript`
2095
- // alongside the rest.
2096
- const contracts = await cm.getContracts();
2097
- const addrByScript = new Map(contracts.map((c) => [c.script, c.address]));
2098
- const byScript = new Map();
2099
- for (const v of spentVtxos) {
2100
- if (!v.script) {
2101
- throw new Error(`Wallet.updateDbAfterSettle: spent VTXO ${v.txid}:${v.vout} has no script`);
2102
- }
2103
- const arr = byScript.get(v.script) ?? [];
2104
- arr.push(v);
2105
- byScript.set(v.script, arr);
2106
- }
2107
- for (const [script, vtxos] of byScript) {
2108
- // User-initiated settle path: refuse to record a settle
2109
- // against the wrong script.
2110
- (0, vtxoOwnership_1.validateVtxosForScript)(vtxos, script, "Wallet.updateDbAfterSettle");
2111
- const targetAddr = addrByScript.get(script);
2112
- if (!targetAddr) {
2113
- throw new Error(`Wallet.updateDbAfterSettle: no contract owns script ${script}`);
2114
- }
2115
- await (0, vtxoOwnership_1.saveVtxosForContract)(this.walletRepository, { script, address: targetAddr }, vtxos);
2116
- }
2117
- }
2118
- if (boardingUtxoToRemove.size > 0) {
2119
- const currentUtxos = await this.walletRepository.getUtxos(boardingAddress);
2120
- const filtered = currentUtxos.filter((u) => !boardingUtxoToRemove.has(`${u.txid}:${u.vout}`));
2121
- // Clear and re-save the filtered list
2122
- await this.walletRepository.deleteUtxos(boardingAddress);
2123
- if (filtered.length > 0) {
2124
- await this.walletRepository.saveUtxos(boardingAddress, filtered);
2125
- }
2126
- }
2127
- }
2128
- catch (e) {
2129
- console.warn("error updating repository after settle", e);
2130
- throw e;
2131
- }
2132
- }
2133
- }
2134
- exports.Wallet = Wallet;
2135
- Wallet.MIN_FEE_RATE = 1; // sats/vbyte
2136
- /**
2137
- * Select virtual outputs to reach a target amount, prioritizing those closer to expiry
2138
- * @param coins List of virtual outputs to select from
2139
- * @param targetAmount Target amount to reach in satoshis
2140
- * @returns Selected virtual outputs and change amount
2141
- */
2142
- function selectVirtualCoins(coins, targetAmount) {
2143
- // Sort virtual outputs by expiry (ascending) and amount (descending)
2144
- const sortedCoins = [...coins].sort((a, b) => {
2145
- // First sort by expiry if available
2146
- const expiryA = a.virtualStatus.batchExpiry || Number.MAX_SAFE_INTEGER;
2147
- const expiryB = b.virtualStatus.batchExpiry || Number.MAX_SAFE_INTEGER;
2148
- if (expiryA !== expiryB) {
2149
- return expiryA - expiryB; // Earlier expiry first
2150
- }
2151
- // Then sort by amount
2152
- return b.value - a.value; // Larger amount first
2153
- });
2154
- const selectedCoins = [];
2155
- let selectedAmount = 0;
2156
- // Select coins until we have enough
2157
- for (const coin of sortedCoins) {
2158
- selectedCoins.push(coin);
2159
- selectedAmount += coin.value;
2160
- if (selectedAmount >= targetAmount) {
2161
- break;
2162
- }
2163
- }
2164
- if (selectedAmount === targetAmount) {
2165
- return { inputs: selectedCoins, changeAmount: 0n };
2166
- }
2167
- // Check if we have enough
2168
- if (selectedAmount < targetAmount) {
2169
- throw new Error("Insufficient funds");
2170
- }
2171
- const changeAmount = BigInt(selectedAmount - targetAmount);
2172
- return {
2173
- inputs: selectedCoins,
2174
- changeAmount,
2175
- };
2176
- }
2177
- /**
2178
- * Wait for incoming funds to the wallet
2179
- * @param wallet - The wallet to wait for incoming funds
2180
- * @returns A promise that resolves the next new coins received by the wallet's address
2181
- */
2182
- async function waitForIncomingFunds(wallet) {
2183
- let stopFunc;
2184
- return new Promise((resolve) => {
2185
- wallet
2186
- .notifyIncomingFunds((coins) => {
2187
- resolve(coins);
2188
- if (stopFunc)
2189
- stopFunc();
2190
- })
2191
- .then((stop) => {
2192
- stopFunc = stop;
2193
- });
2194
- });
2195
- }