@arkade-os/sdk 0.4.26 → 0.4.28

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