@atomiqlabs/sdk 8.8.3 → 8.8.4

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 (339) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +1760 -1760
  3. package/dist/SmartChainAssets.d.ts +181 -181
  4. package/dist/SmartChainAssets.js +181 -181
  5. package/dist/bitcoin/coinselect2/accumulative.d.ts +7 -7
  6. package/dist/bitcoin/coinselect2/accumulative.js +52 -52
  7. package/dist/bitcoin/coinselect2/blackjack.d.ts +7 -7
  8. package/dist/bitcoin/coinselect2/blackjack.js +38 -38
  9. package/dist/bitcoin/coinselect2/index.d.ts +20 -20
  10. package/dist/bitcoin/coinselect2/index.js +69 -69
  11. package/dist/bitcoin/coinselect2/utils.d.ts +82 -82
  12. package/dist/bitcoin/coinselect2/utils.js +158 -158
  13. package/dist/bitcoin/wallet/BitcoinWallet.d.ts +113 -113
  14. package/dist/bitcoin/wallet/BitcoinWallet.js +335 -335
  15. package/dist/bitcoin/wallet/IBitcoinWallet.d.ts +116 -116
  16. package/dist/bitcoin/wallet/IBitcoinWallet.js +21 -21
  17. package/dist/bitcoin/wallet/SingleAddressBitcoinWallet.d.ts +106 -106
  18. package/dist/bitcoin/wallet/SingleAddressBitcoinWallet.js +196 -196
  19. package/dist/enums/FeeType.d.ts +15 -15
  20. package/dist/enums/FeeType.js +19 -19
  21. package/dist/enums/SwapAmountType.d.ts +15 -15
  22. package/dist/enums/SwapAmountType.js +19 -19
  23. package/dist/enums/SwapDirection.d.ts +15 -15
  24. package/dist/enums/SwapDirection.js +19 -19
  25. package/dist/enums/SwapSide.d.ts +15 -15
  26. package/dist/enums/SwapSide.js +19 -19
  27. package/dist/enums/SwapType.d.ts +75 -75
  28. package/dist/enums/SwapType.js +79 -79
  29. package/dist/errors/IntermediaryError.d.ts +13 -13
  30. package/dist/errors/IntermediaryError.js +27 -27
  31. package/dist/errors/RequestError.d.ts +32 -32
  32. package/dist/errors/RequestError.js +54 -54
  33. package/dist/errors/UserError.d.ts +8 -8
  34. package/dist/errors/UserError.js +16 -16
  35. package/dist/events/UnifiedSwapEventListener.d.ts +23 -23
  36. package/dist/events/UnifiedSwapEventListener.js +132 -132
  37. package/dist/http/HttpUtils.d.ts +27 -27
  38. package/dist/http/HttpUtils.js +91 -91
  39. package/dist/http/paramcoders/IParamReader.d.ts +8 -8
  40. package/dist/http/paramcoders/IParamReader.js +2 -2
  41. package/dist/http/paramcoders/ParamDecoder.d.ts +44 -44
  42. package/dist/http/paramcoders/ParamDecoder.js +137 -137
  43. package/dist/http/paramcoders/ParamEncoder.d.ts +20 -20
  44. package/dist/http/paramcoders/ParamEncoder.js +36 -36
  45. package/dist/http/paramcoders/SchemaVerifier.d.ts +26 -26
  46. package/dist/http/paramcoders/SchemaVerifier.js +145 -145
  47. package/dist/http/paramcoders/client/ResponseParamDecoder.d.ts +11 -11
  48. package/dist/http/paramcoders/client/ResponseParamDecoder.js +57 -57
  49. package/dist/http/paramcoders/client/StreamParamEncoder.d.ts +13 -13
  50. package/dist/http/paramcoders/client/StreamParamEncoder.js +26 -26
  51. package/dist/http/paramcoders/client/StreamingFetchPromise.d.ts +16 -16
  52. package/dist/http/paramcoders/client/StreamingFetchPromise.js +174 -174
  53. package/dist/index.d.ts +85 -85
  54. package/dist/index.js +158 -158
  55. package/dist/intermediaries/Intermediary.d.ts +178 -178
  56. package/dist/intermediaries/Intermediary.js +166 -166
  57. package/dist/intermediaries/IntermediaryDiscovery.d.ts +211 -211
  58. package/dist/intermediaries/IntermediaryDiscovery.js +424 -424
  59. package/dist/intermediaries/apis/IntermediaryAPI.d.ts +450 -450
  60. package/dist/intermediaries/apis/IntermediaryAPI.js +618 -618
  61. package/dist/intermediaries/apis/TrustedIntermediaryAPI.d.ts +155 -155
  62. package/dist/intermediaries/apis/TrustedIntermediaryAPI.js +137 -137
  63. package/dist/lnurl/LNURL.d.ts +102 -102
  64. package/dist/lnurl/LNURL.js +321 -321
  65. package/dist/prices/RedundantSwapPrice.d.ts +110 -110
  66. package/dist/prices/RedundantSwapPrice.js +222 -222
  67. package/dist/prices/SingleSwapPrice.d.ts +34 -34
  68. package/dist/prices/SingleSwapPrice.js +44 -44
  69. package/dist/prices/SwapPriceWithChain.d.ts +107 -107
  70. package/dist/prices/SwapPriceWithChain.js +128 -128
  71. package/dist/prices/abstract/ICachedSwapPrice.d.ts +28 -28
  72. package/dist/prices/abstract/ICachedSwapPrice.js +62 -62
  73. package/dist/prices/abstract/IPriceProvider.d.ts +81 -81
  74. package/dist/prices/abstract/IPriceProvider.js +74 -74
  75. package/dist/prices/abstract/ISwapPrice.d.ts +168 -168
  76. package/dist/prices/abstract/ISwapPrice.js +279 -279
  77. package/dist/prices/providers/BinancePriceProvider.d.ts +23 -23
  78. package/dist/prices/providers/BinancePriceProvider.js +30 -30
  79. package/dist/prices/providers/CoinGeckoPriceProvider.d.ts +23 -23
  80. package/dist/prices/providers/CoinGeckoPriceProvider.js +29 -29
  81. package/dist/prices/providers/CoinPaprikaPriceProvider.d.ts +25 -25
  82. package/dist/prices/providers/CoinPaprikaPriceProvider.js +29 -29
  83. package/dist/prices/providers/CustomPriceProvider.d.ts +24 -24
  84. package/dist/prices/providers/CustomPriceProvider.js +35 -35
  85. package/dist/prices/providers/KrakenPriceProvider.d.ts +38 -38
  86. package/dist/prices/providers/KrakenPriceProvider.js +45 -45
  87. package/dist/prices/providers/OKXPriceProvider.d.ts +34 -34
  88. package/dist/prices/providers/OKXPriceProvider.js +29 -29
  89. package/dist/prices/providers/abstract/ExchangePriceProvider.d.ts +17 -17
  90. package/dist/prices/providers/abstract/ExchangePriceProvider.js +21 -21
  91. package/dist/prices/providers/abstract/HttpPriceProvider.d.ts +7 -7
  92. package/dist/prices/providers/abstract/HttpPriceProvider.js +12 -12
  93. package/dist/storage/IUnifiedStorage.d.ts +85 -85
  94. package/dist/storage/IUnifiedStorage.js +2 -2
  95. package/dist/storage/UnifiedSwapStorage.d.ts +114 -114
  96. package/dist/storage/UnifiedSwapStorage.js +116 -116
  97. package/dist/storage-browser/IndexedDBUnifiedStorage.d.ts +63 -63
  98. package/dist/storage-browser/IndexedDBUnifiedStorage.js +298 -298
  99. package/dist/storage-browser/LocalStorageManager.d.ts +49 -49
  100. package/dist/storage-browser/LocalStorageManager.js +93 -93
  101. package/dist/swapper/Swapper.d.ts +732 -732
  102. package/dist/swapper/Swapper.js +1713 -1713
  103. package/dist/swapper/SwapperFactory.d.ts +135 -135
  104. package/dist/swapper/SwapperFactory.js +162 -162
  105. package/dist/swapper/SwapperUtils.d.ts +206 -206
  106. package/dist/swapper/SwapperUtils.js +481 -481
  107. package/dist/swapper/SwapperWithChain.d.ts +404 -404
  108. package/dist/swapper/SwapperWithChain.js +469 -469
  109. package/dist/swapper/SwapperWithSigner.d.ts +322 -322
  110. package/dist/swapper/SwapperWithSigner.js +318 -318
  111. package/dist/swaps/IAddressSwap.d.ts +22 -22
  112. package/dist/swaps/IAddressSwap.js +14 -14
  113. package/dist/swaps/IBTCWalletSwap.d.ts +73 -73
  114. package/dist/swaps/IBTCWalletSwap.js +18 -18
  115. package/dist/swaps/IClaimableSwap.d.ts +49 -49
  116. package/dist/swaps/IClaimableSwap.js +15 -15
  117. package/dist/swaps/IClaimableSwapWrapper.d.ts +15 -15
  118. package/dist/swaps/IClaimableSwapWrapper.js +2 -2
  119. package/dist/swaps/IRefundableSwap.d.ts +43 -43
  120. package/dist/swaps/IRefundableSwap.js +14 -14
  121. package/dist/swaps/ISwap.d.ts +392 -392
  122. package/dist/swaps/ISwap.js +349 -349
  123. package/dist/swaps/ISwapWithGasDrop.d.ts +21 -21
  124. package/dist/swaps/ISwapWithGasDrop.js +12 -12
  125. package/dist/swaps/ISwapWrapper.d.ts +285 -285
  126. package/dist/swaps/ISwapWrapper.js +353 -353
  127. package/dist/swaps/escrow_swaps/IEscrowSelfInitSwap.d.ts +98 -98
  128. package/dist/swaps/escrow_swaps/IEscrowSelfInitSwap.js +126 -126
  129. package/dist/swaps/escrow_swaps/IEscrowSwap.d.ts +139 -139
  130. package/dist/swaps/escrow_swaps/IEscrowSwap.js +170 -170
  131. package/dist/swaps/escrow_swaps/IEscrowSwapWrapper.d.ts +128 -128
  132. package/dist/swaps/escrow_swaps/IEscrowSwapWrapper.js +167 -167
  133. package/dist/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.d.ts +105 -105
  134. package/dist/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.js +129 -129
  135. package/dist/swaps/escrow_swaps/frombtc/IFromBTCSelfInitSwap.d.ts +162 -162
  136. package/dist/swaps/escrow_swaps/frombtc/IFromBTCSelfInitSwap.js +190 -190
  137. package/dist/swaps/escrow_swaps/frombtc/IFromBTCWrapper.d.ts +64 -64
  138. package/dist/swaps/escrow_swaps/frombtc/IFromBTCWrapper.js +82 -82
  139. package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.d.ts +531 -531
  140. package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.js +1285 -1285
  141. package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.d.ts +190 -190
  142. package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.js +432 -432
  143. package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.d.ts +583 -583
  144. package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.js +1371 -1371
  145. package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.d.ts +235 -235
  146. package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.js +525 -525
  147. package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.d.ts +458 -458
  148. package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.js +1126 -1126
  149. package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.d.ts +202 -202
  150. package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.js +406 -406
  151. package/dist/swaps/escrow_swaps/tobtc/IToBTCSwap.d.ts +403 -403
  152. package/dist/swaps/escrow_swaps/tobtc/IToBTCSwap.js +924 -924
  153. package/dist/swaps/escrow_swaps/tobtc/IToBTCWrapper.d.ts +68 -68
  154. package/dist/swaps/escrow_swaps/tobtc/IToBTCWrapper.js +117 -117
  155. package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNSwap.d.ts +127 -127
  156. package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNSwap.js +256 -256
  157. package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.d.ts +251 -251
  158. package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.js +536 -536
  159. package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCSwap.d.ts +73 -73
  160. package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCSwap.js +155 -155
  161. package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.d.ts +132 -132
  162. package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.js +286 -286
  163. package/dist/swaps/spv_swaps/SpvFromBTCSwap.d.ts +637 -637
  164. package/dist/swaps/spv_swaps/SpvFromBTCSwap.js +1448 -1448
  165. package/dist/swaps/spv_swaps/SpvFromBTCWrapper.d.ts +257 -257
  166. package/dist/swaps/spv_swaps/SpvFromBTCWrapper.js +947 -947
  167. package/dist/swaps/trusted/ln/LnForGasSwap.d.ts +261 -261
  168. package/dist/swaps/trusted/ln/LnForGasSwap.js +511 -511
  169. package/dist/swaps/trusted/ln/LnForGasWrapper.d.ts +40 -40
  170. package/dist/swaps/trusted/ln/LnForGasWrapper.js +83 -83
  171. package/dist/swaps/trusted/onchain/OnchainForGasSwap.d.ts +342 -342
  172. package/dist/swaps/trusted/onchain/OnchainForGasSwap.js +715 -715
  173. package/dist/swaps/trusted/onchain/OnchainForGasWrapper.d.ts +69 -69
  174. package/dist/swaps/trusted/onchain/OnchainForGasWrapper.js +93 -93
  175. package/dist/types/AmountData.d.ts +10 -10
  176. package/dist/types/AmountData.js +2 -2
  177. package/dist/types/CustomPriceFunction.d.ts +11 -11
  178. package/dist/types/CustomPriceFunction.js +2 -2
  179. package/dist/types/PriceInfoType.d.ts +28 -28
  180. package/dist/types/PriceInfoType.js +57 -57
  181. package/dist/types/SwapExecutionAction.d.ts +88 -88
  182. package/dist/types/SwapExecutionAction.js +2 -2
  183. package/dist/types/SwapStateInfo.d.ts +5 -5
  184. package/dist/types/SwapStateInfo.js +2 -2
  185. package/dist/types/SwapWithSigner.d.ts +17 -17
  186. package/dist/types/SwapWithSigner.js +43 -43
  187. package/dist/types/Token.d.ts +99 -99
  188. package/dist/types/Token.js +76 -76
  189. package/dist/types/TokenAmount.d.ts +69 -69
  190. package/dist/types/TokenAmount.js +60 -60
  191. package/dist/types/fees/Fee.d.ts +50 -50
  192. package/dist/types/fees/Fee.js +2 -2
  193. package/dist/types/fees/FeeBreakdown.d.ts +11 -11
  194. package/dist/types/fees/FeeBreakdown.js +2 -2
  195. package/dist/types/fees/PercentagePPM.d.ts +17 -17
  196. package/dist/types/fees/PercentagePPM.js +18 -18
  197. package/dist/types/lnurl/LNURLPay.d.ts +61 -61
  198. package/dist/types/lnurl/LNURLPay.js +31 -31
  199. package/dist/types/lnurl/LNURLWithdraw.d.ts +48 -48
  200. package/dist/types/lnurl/LNURLWithdraw.js +27 -27
  201. package/dist/types/wallets/LightningInvoiceCreateService.d.ts +24 -24
  202. package/dist/types/wallets/LightningInvoiceCreateService.js +15 -15
  203. package/dist/types/wallets/MinimalBitcoinWalletInterface.d.ts +23 -23
  204. package/dist/types/wallets/MinimalBitcoinWalletInterface.js +2 -2
  205. package/dist/types/wallets/MinimalLightningNetworkWalletInterface.d.ts +9 -9
  206. package/dist/types/wallets/MinimalLightningNetworkWalletInterface.js +2 -2
  207. package/dist/utils/AutomaticClockDriftCorrection.d.ts +1 -1
  208. package/dist/utils/AutomaticClockDriftCorrection.js +70 -70
  209. package/dist/utils/BitcoinUtils.d.ts +16 -16
  210. package/dist/utils/BitcoinUtils.js +141 -141
  211. package/dist/utils/BitcoinWalletUtils.d.ts +7 -7
  212. package/dist/utils/BitcoinWalletUtils.js +14 -14
  213. package/dist/utils/Logger.d.ts +7 -7
  214. package/dist/utils/Logger.js +12 -12
  215. package/dist/utils/RetryUtils.d.ts +22 -22
  216. package/dist/utils/RetryUtils.js +67 -67
  217. package/dist/utils/SwapUtils.d.ts +88 -88
  218. package/dist/utils/SwapUtils.js +72 -72
  219. package/dist/utils/TimeoutUtils.d.ts +17 -17
  220. package/dist/utils/TimeoutUtils.js +55 -55
  221. package/dist/utils/TokenUtils.d.ts +19 -19
  222. package/dist/utils/TokenUtils.js +37 -37
  223. package/dist/utils/TypeUtils.d.ts +7 -7
  224. package/dist/utils/TypeUtils.js +2 -2
  225. package/dist/utils/Utils.d.ts +67 -67
  226. package/dist/utils/Utils.js +208 -208
  227. package/package.json +43 -43
  228. package/src/SmartChainAssets.ts +186 -186
  229. package/src/bitcoin/coinselect2/accumulative.ts +69 -69
  230. package/src/bitcoin/coinselect2/blackjack.ts +50 -50
  231. package/src/bitcoin/coinselect2/index.ts +93 -93
  232. package/src/bitcoin/coinselect2/utils.ts +236 -236
  233. package/src/bitcoin/wallet/BitcoinWallet.ts +439 -439
  234. package/src/bitcoin/wallet/IBitcoinWallet.ts +140 -140
  235. package/src/bitcoin/wallet/SingleAddressBitcoinWallet.ts +225 -225
  236. package/src/enums/FeeType.ts +15 -15
  237. package/src/enums/SwapAmountType.ts +16 -16
  238. package/src/enums/SwapDirection.ts +15 -15
  239. package/src/enums/SwapSide.ts +16 -16
  240. package/src/enums/SwapType.ts +75 -75
  241. package/src/errors/IntermediaryError.ts +28 -28
  242. package/src/errors/RequestError.ts +64 -64
  243. package/src/errors/UserError.ts +15 -15
  244. package/src/events/UnifiedSwapEventListener.ts +173 -173
  245. package/src/http/HttpUtils.ts +91 -91
  246. package/src/http/paramcoders/IParamReader.ts +9 -9
  247. package/src/http/paramcoders/ParamDecoder.ts +145 -145
  248. package/src/http/paramcoders/ParamEncoder.ts +40 -40
  249. package/src/http/paramcoders/SchemaVerifier.ts +153 -153
  250. package/src/http/paramcoders/client/ResponseParamDecoder.ts +57 -57
  251. package/src/http/paramcoders/client/StreamParamEncoder.ts +28 -28
  252. package/src/http/paramcoders/client/StreamingFetchPromise.ts +192 -192
  253. package/src/index.ts +140 -140
  254. package/src/intermediaries/Intermediary.ts +280 -280
  255. package/src/intermediaries/IntermediaryDiscovery.ts +541 -541
  256. package/src/intermediaries/apis/IntermediaryAPI.ts +963 -963
  257. package/src/intermediaries/apis/TrustedIntermediaryAPI.ts +257 -257
  258. package/src/lnurl/LNURL.ts +402 -402
  259. package/src/prices/RedundantSwapPrice.ts +264 -264
  260. package/src/prices/SingleSwapPrice.ts +50 -50
  261. package/src/prices/SwapPriceWithChain.ts +194 -194
  262. package/src/prices/abstract/ICachedSwapPrice.ts +85 -85
  263. package/src/prices/abstract/IPriceProvider.ts +127 -127
  264. package/src/prices/abstract/ISwapPrice.ts +390 -390
  265. package/src/prices/providers/BinancePriceProvider.ts +48 -48
  266. package/src/prices/providers/CoinGeckoPriceProvider.ts +46 -46
  267. package/src/prices/providers/CoinPaprikaPriceProvider.ts +49 -49
  268. package/src/prices/providers/CustomPriceProvider.ts +40 -40
  269. package/src/prices/providers/KrakenPriceProvider.ts +83 -83
  270. package/src/prices/providers/OKXPriceProvider.ts +59 -59
  271. package/src/prices/providers/abstract/ExchangePriceProvider.ts +31 -31
  272. package/src/prices/providers/abstract/HttpPriceProvider.ts +14 -14
  273. package/src/storage/IUnifiedStorage.ts +95 -95
  274. package/src/storage/UnifiedSwapStorage.ts +141 -141
  275. package/src/storage-browser/IndexedDBUnifiedStorage.ts +350 -350
  276. package/src/storage-browser/LocalStorageManager.ts +106 -106
  277. package/src/swapper/Swapper.ts +2488 -2488
  278. package/src/swapper/SwapperFactory.ts +307 -307
  279. package/src/swapper/SwapperUtils.ts +570 -570
  280. package/src/swapper/SwapperWithChain.ts +707 -707
  281. package/src/swapper/SwapperWithSigner.ts +511 -511
  282. package/src/swaps/IAddressSwap.ts +30 -30
  283. package/src/swaps/IBTCWalletSwap.ts +92 -92
  284. package/src/swaps/IClaimableSwap.ts +65 -65
  285. package/src/swaps/IClaimableSwapWrapper.ts +17 -17
  286. package/src/swaps/IRefundableSwap.ts +58 -58
  287. package/src/swaps/ISwap.ts +703 -703
  288. package/src/swaps/ISwapWithGasDrop.ts +25 -25
  289. package/src/swaps/ISwapWrapper.ts +539 -539
  290. package/src/swaps/escrow_swaps/IEscrowSelfInitSwap.ts +217 -217
  291. package/src/swaps/escrow_swaps/IEscrowSwap.ts +269 -269
  292. package/src/swaps/escrow_swaps/IEscrowSwapWrapper.ts +282 -282
  293. package/src/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.ts +169 -169
  294. package/src/swaps/escrow_swaps/frombtc/IFromBTCSelfInitSwap.ts +300 -300
  295. package/src/swaps/escrow_swaps/frombtc/IFromBTCWrapper.ts +107 -107
  296. package/src/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.ts +1473 -1474
  297. package/src/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.ts +601 -601
  298. package/src/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.ts +1582 -1582
  299. package/src/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.ts +750 -750
  300. package/src/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.ts +1299 -1299
  301. package/src/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.ts +610 -610
  302. package/src/swaps/escrow_swaps/tobtc/IToBTCSwap.ts +1096 -1096
  303. package/src/swaps/escrow_swaps/tobtc/IToBTCWrapper.ts +138 -138
  304. package/src/swaps/escrow_swaps/tobtc/ln/ToBTCLNSwap.ts +304 -304
  305. package/src/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.ts +786 -786
  306. package/src/swaps/escrow_swaps/tobtc/onchain/ToBTCSwap.ts +206 -206
  307. package/src/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.ts +401 -401
  308. package/src/swaps/spv_swaps/SpvFromBTCSwap.ts +1812 -1812
  309. package/src/swaps/spv_swaps/SpvFromBTCWrapper.ts +1236 -1236
  310. package/src/swaps/trusted/ln/LnForGasSwap.ts +589 -589
  311. package/src/swaps/trusted/ln/LnForGasWrapper.ts +91 -91
  312. package/src/swaps/trusted/onchain/OnchainForGasSwap.ts +862 -862
  313. package/src/swaps/trusted/onchain/OnchainForGasWrapper.ts +131 -131
  314. package/src/types/AmountData.ts +9 -9
  315. package/src/types/CustomPriceFunction.ts +11 -11
  316. package/src/types/PriceInfoType.ts +66 -66
  317. package/src/types/SwapExecutionAction.ts +99 -99
  318. package/src/types/SwapStateInfo.ts +6 -6
  319. package/src/types/SwapWithSigner.ts +61 -61
  320. package/src/types/Token.ts +163 -163
  321. package/src/types/TokenAmount.ts +132 -132
  322. package/src/types/fees/Fee.ts +56 -56
  323. package/src/types/fees/FeeBreakdown.ts +11 -11
  324. package/src/types/fees/PercentagePPM.ts +26 -26
  325. package/src/types/lnurl/LNURLPay.ts +79 -79
  326. package/src/types/lnurl/LNURLWithdraw.ts +61 -61
  327. package/src/types/wallets/LightningInvoiceCreateService.ts +30 -30
  328. package/src/types/wallets/MinimalBitcoinWalletInterface.ts +21 -21
  329. package/src/types/wallets/MinimalLightningNetworkWalletInterface.ts +9 -9
  330. package/src/utils/AutomaticClockDriftCorrection.ts +71 -71
  331. package/src/utils/BitcoinUtils.ts +132 -132
  332. package/src/utils/BitcoinWalletUtils.ts +15 -15
  333. package/src/utils/Logger.ts +14 -14
  334. package/src/utils/RetryUtils.ts +78 -78
  335. package/src/utils/SwapUtils.ts +99 -99
  336. package/src/utils/TimeoutUtils.ts +49 -49
  337. package/src/utils/TokenUtils.ts +33 -33
  338. package/src/utils/TypeUtils.ts +8 -8
  339. package/src/utils/Utils.ts +212 -212
@@ -1,1299 +1,1299 @@
1
- import {IFromBTCSelfInitSwap} from "../IFromBTCSelfInitSwap";
2
- import {SwapType} from "../../../../enums/SwapType";
3
- import {FromBTCDefinition, FromBTCWrapper} from "./FromBTCWrapper";
4
- import {
5
- BtcTxWithBlockheight,
6
- ChainType,
7
- isAbstractSigner,
8
- SwapCommitState,
9
- SwapCommitStateType,
10
- SwapData
11
- } from "@atomiqlabs/base";
12
- import {Buffer} from "buffer";
13
- import {
14
- extendAbortController,
15
- getTxoHash, toBigInt
16
- } from "../../../../utils/Utils";
17
- import {
18
- fromOutputScript,
19
- parsePsbtTransaction,
20
- toOutputScript,
21
- } from "../../../../utils/BitcoinUtils";
22
- import {IBitcoinWallet, isIBitcoinWallet} from "../../../../bitcoin/wallet/IBitcoinWallet";
23
- import {IBTCWalletSwap} from "../../../IBTCWalletSwap";
24
- import {Transaction} from "@scure/btc-signer";
25
- import {SingleAddressBitcoinWallet} from "../../../../bitcoin/wallet/SingleAddressBitcoinWallet";
26
- import {
27
- MinimalBitcoinWalletInterface,
28
- MinimalBitcoinWalletInterfaceWithSigner
29
- } from "../../../../types/wallets/MinimalBitcoinWalletInterface";
30
- import {IClaimableSwap} from "../../../IClaimableSwap";
31
- import {IEscrowSelfInitSwapInit, isIEscrowSelfInitSwapInit} from "../../IEscrowSelfInitSwap";
32
- import {IAddressSwap} from "../../../IAddressSwap";
33
- import {TokenAmount, toTokenAmount} from "../../../../types/TokenAmount";
34
- import {BitcoinTokens, BtcToken, SCToken} from "../../../../types/Token";
35
- import {getLogger, LoggerType} from "../../../../utils/Logger";
36
- import {toBitcoinWallet} from "../../../../utils/BitcoinWalletUtils";
37
- import {SwapExecutionAction} from "../../../../types/SwapExecutionAction";
38
-
39
- /**
40
- * State enum for legacy escrow based Bitcoin -> Smart chain swaps.
41
- *
42
- * @category Swaps/Legacy/Bitcoin → Smart chain
43
- */
44
- export enum FromBTCSwapState {
45
- /**
46
- * Bitcoin swap address has expired and the intermediary (LP) has already refunded
47
- * its funds. No BTC should be sent anymore!
48
- */
49
- FAILED = -4,
50
- /**
51
- * Bitcoin swap address has expired, user should not send any BTC anymore! Though
52
- * the intermediary (LP) hasn't refunded yet. So if there is a transaction already
53
- * in-flight the swap might still succeed.
54
- */
55
- EXPIRED = -3,
56
- /**
57
- * Swap has expired for good and there is no way how it can be executed anymore
58
- */
59
- QUOTE_EXPIRED = -2,
60
- /**
61
- * A swap is almost expired, and it should be presented to the user as expired, though
62
- * there is still a chance that it will be processed
63
- */
64
- QUOTE_SOFT_EXPIRED = -1,
65
- /**
66
- * Swap quote was created, use the {@link FromBTCSwap.commit} or {@link FromBTCSwap.txsCommit} functions
67
- * to initiate it by creating the swap escrow on the destination smart chain
68
- */
69
- PR_CREATED = 0,
70
- /**
71
- * Swap escrow was initiated (committed) on the destination chain, user can send the BTC to the
72
- * swap address with the {@link FromBTCSwap.getFundedPsbt}, {@link FromBTCSwap.getAddress} or
73
- * {@link FromBTCSwap.getHyperlink} functions.
74
- */
75
- CLAIM_COMMITED = 1,
76
- /**
77
- * Input bitcoin transaction was confirmed, wait for automatic settlement by the watchtowers
78
- * using the {@link FromBTCSwap.waitTillClaimed} function or settle manually using the {@link FromBTCSwap.claim}
79
- * or {@link FromBTCSwap.txsClaim} function.
80
- */
81
- BTC_TX_CONFIRMED = 2,
82
- /**
83
- * Swap successfully settled and funds received on the destination chain
84
- */
85
- CLAIM_CLAIMED = 3
86
- }
87
-
88
- const FromBTCSwapStateDescription = {
89
- [FromBTCSwapState.FAILED]: "Bitcoin swap address has expired and the intermediary (LP) has already refunded its funds. No BTC should be sent anymore!",
90
- [FromBTCSwapState.EXPIRED]: "Bitcoin swap address has expired, user should not send any BTC anymore! Though the intermediary (LP) hasn't refunded yet. So if there is a transaction already in-flight the swap might still succeed.",
91
- [FromBTCSwapState.QUOTE_EXPIRED]: "Swap has expired for good and there is no way how it can be executed anymore",
92
- [FromBTCSwapState.QUOTE_SOFT_EXPIRED]: "The swap is expired, though there is still a chance that it will be processed",
93
- [FromBTCSwapState.PR_CREATED]: "Swap quote was created, initiate it by creating the swap escrow on the destination smart chain",
94
- [FromBTCSwapState.CLAIM_COMMITED]: "Swap escrow was initiated (committed) on the destination chain, user can send the BTC to the Bitcoin swap address.",
95
- [FromBTCSwapState.BTC_TX_CONFIRMED]: "Input bitcoin transaction was confirmed, wait for automatic settlement by the watchtower or settle manually.",
96
- [FromBTCSwapState.CLAIM_CLAIMED]: "Swap successfully settled and funds received on the destination chain"
97
- };
98
-
99
- export type FromBTCSwapInit<T extends SwapData> = IEscrowSelfInitSwapInit<T> & {
100
- data: T;
101
- address?: string;
102
- amount?: bigint;
103
- requiredConfirmations?: number;
104
- };
105
-
106
- export function isFromBTCSwapInit<T extends SwapData>(obj: any): obj is FromBTCSwapInit<T> {
107
- return typeof(obj.data) === "object" &&
108
- (obj.address==null || typeof(obj.address) === "string") &&
109
- (obj.amount==null || typeof(obj.amount) === "bigint") &&
110
- (obj.requiredConfirmations==null || typeof(obj.requiredConfirmations) === "number") &&
111
- isIEscrowSelfInitSwapInit<T>(obj);
112
- }
113
-
114
- /**
115
- * Legacy escrow (PrTLC) based swap for Bitcoin -> Smart chains, requires manual initiation
116
- * of the swap escrow on the destination chain.
117
- *
118
- * @category Swaps/Legacy/Bitcoin → Smart chain
119
- */
120
- export class FromBTCSwap<T extends ChainType = ChainType>
121
- extends IFromBTCSelfInitSwap<T, FromBTCDefinition<T>, FromBTCSwapState>
122
- implements IBTCWalletSwap, IClaimableSwap<T, FromBTCDefinition<T>, FromBTCSwapState>, IAddressSwap {
123
-
124
- protected readonly TYPE: SwapType.FROM_BTC = SwapType.FROM_BTC;
125
- /**
126
- * @internal
127
- */
128
- protected readonly swapStateName = (state: number) => FromBTCSwapState[state];
129
- /**
130
- * @internal
131
- */
132
- protected readonly swapStateDescription = FromBTCSwapStateDescription;
133
- /**
134
- * @internal
135
- */
136
- protected readonly logger: LoggerType;
137
- /**
138
- * @internal
139
- */
140
- protected readonly inputToken: BtcToken<false> = BitcoinTokens.BTC;
141
- /**
142
- * @internal
143
- */
144
- protected readonly feeRate!: string;
145
-
146
- /**
147
- * @internal
148
- */
149
- readonly _data!: T["Data"];
150
-
151
- private address?: string;
152
- private amount?: bigint;
153
- private requiredConfirmations?: number;
154
-
155
- private senderAddress?: string;
156
- private txId?: string;
157
- private vout?: number;
158
-
159
- private btcTxConfirmedAt?: number;
160
-
161
- constructor(wrapper: FromBTCWrapper<T>, init: FromBTCSwapInit<T["Data"]>);
162
- constructor(wrapper: FromBTCWrapper<T>, obj: any);
163
- constructor(wrapper: FromBTCWrapper<T>, initOrObject: FromBTCSwapInit<T["Data"]> | any) {
164
- if(isFromBTCSwapInit(initOrObject) && initOrObject.url!=null) initOrObject.url += "/frombtc";
165
- super(wrapper, initOrObject);
166
- if(isFromBTCSwapInit(initOrObject)) {
167
- this._state = FromBTCSwapState.PR_CREATED;
168
- this._data = initOrObject.data;
169
- this.feeRate = initOrObject.feeRate;
170
- this.address = initOrObject.address;
171
- this.amount = initOrObject.amount;
172
- this.requiredConfirmations = initOrObject.requiredConfirmations;
173
- } else {
174
- this.address = initOrObject.address;
175
- this.amount = toBigInt(initOrObject.amount);
176
- this.senderAddress = initOrObject.senderAddress;
177
- this.txId = initOrObject.txId;
178
- this.vout = initOrObject.vout;
179
- this.requiredConfirmations = initOrObject.requiredConfirmations ?? this._data.getConfirmationsHint();
180
- this.btcTxConfirmedAt = initOrObject.btcTxConfirmedAt;
181
- }
182
- this.tryRecomputeSwapPrice();
183
- this.logger = getLogger("FromBTC("+this.getIdentifierHashString()+"): ");
184
- }
185
-
186
- /**
187
- * @inheritDoc
188
- * @internal
189
- */
190
- protected getSwapData(): T["Data"] {
191
- return this._data;
192
- }
193
-
194
- /**
195
- * @inheritDoc
196
- * @internal
197
- */
198
- protected upgradeVersion() {
199
- if(this.version == null) {
200
- switch(this._state) {
201
- case -2:
202
- this._state = FromBTCSwapState.FAILED
203
- break;
204
- case -1:
205
- this._state = FromBTCSwapState.QUOTE_EXPIRED
206
- break;
207
- case 0:
208
- this._state = FromBTCSwapState.PR_CREATED
209
- break;
210
- case 1:
211
- this._state = FromBTCSwapState.CLAIM_COMMITED
212
- break;
213
- case 2:
214
- this._state = FromBTCSwapState.BTC_TX_CONFIRMED
215
- break;
216
- case 3:
217
- this._state = FromBTCSwapState.CLAIM_CLAIMED
218
- break;
219
- }
220
- this.version = 1;
221
- }
222
- }
223
-
224
-
225
- //////////////////////////////
226
- //// Getters & utils
227
-
228
- /**
229
- * Returns bitcoin address where the on-chain BTC should be sent to
230
- */
231
- getAddress(): string {
232
- if(this._state===FromBTCSwapState.PR_CREATED) throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
233
- return this.address ?? "";
234
- }
235
-
236
- /**
237
- * Unsafe bitcoin hyperlink getter, returns the address even before the swap is committed!
238
- *
239
- * @private
240
- */
241
- private _getHyperlink(): string {
242
- return this.address==null || this.amount==null ? "" : "bitcoin:"+this.address+"?amount="+encodeURIComponent((Number(this.amount) / 100000000).toString(10));
243
- }
244
-
245
- /**
246
- * @inheritDoc
247
- */
248
- getHyperlink(): string {
249
- if(this._state===FromBTCSwapState.PR_CREATED) throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
250
- return this._getHyperlink();
251
- }
252
-
253
- /**
254
- * @inheritDoc
255
- */
256
- getInputAddress(): string | null {
257
- return this.senderAddress ?? null;
258
- }
259
-
260
- /**
261
- * @inheritDoc
262
- */
263
- getInputTxId(): string | null {
264
- return this.txId ?? null;
265
- }
266
-
267
- /**
268
- * Returns timeout time (in UNIX milliseconds) when the on-chain address will expire and no funds should be sent
269
- * to that address anymore
270
- */
271
- getTimeoutTime(): number {
272
- return Number(this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations ?? 6)) * 1000;
273
- }
274
-
275
- /**
276
- * @inheritDoc
277
- */
278
- requiresAction(): boolean {
279
- return this.isClaimable() || (this._state===FromBTCSwapState.CLAIM_COMMITED && this.getTimeoutTime()>Date.now() && this.txId==null);
280
- }
281
-
282
- /**
283
- * @inheritDoc
284
- */
285
- isFinished(): boolean {
286
- return this._state===FromBTCSwapState.CLAIM_CLAIMED || this._state===FromBTCSwapState.QUOTE_EXPIRED || this._state===FromBTCSwapState.FAILED;
287
- }
288
-
289
- /**
290
- * @inheritDoc
291
- */
292
- isClaimable(): boolean {
293
- return this._state===FromBTCSwapState.BTC_TX_CONFIRMED;
294
- }
295
-
296
- /**
297
- * @inheritDoc
298
- */
299
- isSuccessful(): boolean {
300
- return this._state===FromBTCSwapState.CLAIM_CLAIMED;
301
- }
302
-
303
- /**
304
- * @inheritDoc
305
- */
306
- isFailed(): boolean {
307
- return this._state===FromBTCSwapState.FAILED || this._state===FromBTCSwapState.EXPIRED;
308
- }
309
-
310
- /**
311
- * @inheritDoc
312
- */
313
- isInProgress(): boolean {
314
- return this._state===FromBTCSwapState.CLAIM_COMMITED ||
315
- this._state===FromBTCSwapState.BTC_TX_CONFIRMED;
316
- }
317
-
318
- /**
319
- * @inheritDoc
320
- */
321
- isQuoteExpired(): boolean {
322
- return this._state===FromBTCSwapState.QUOTE_EXPIRED;
323
- }
324
-
325
- /**
326
- * @inheritDoc
327
- */
328
- isQuoteSoftExpired(): boolean {
329
- return this._state===FromBTCSwapState.QUOTE_EXPIRED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED;
330
- }
331
-
332
- /**
333
- * @inheritDoc
334
- * @internal
335
- */
336
- protected canCommit(skipQuoteExpiryChecks?: boolean): boolean {
337
- if(this._state!==FromBTCSwapState.PR_CREATED && (!skipQuoteExpiryChecks || this._state!==FromBTCSwapState.QUOTE_SOFT_EXPIRED)) return false;
338
- if(this.requiredConfirmations==null) return false;
339
- const expiry = this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations);
340
- const currentTimestamp = BigInt(Math.floor(Date.now()/1000));
341
-
342
- return (expiry - currentTimestamp) >= this.wrapper._options.minSendWindow;
343
- }
344
-
345
-
346
- //////////////////////////////
347
- //// Amounts & fees
348
-
349
- /**
350
- * @inheritDoc
351
- */
352
- getInputToken(): BtcToken<false> {
353
- return BitcoinTokens.BTC;
354
- }
355
-
356
- /**
357
- * @inheritDoc
358
- */
359
- getInput(): TokenAmount<BtcToken<false>> {
360
- return toTokenAmount(this.amount ?? null, this.inputToken, this.wrapper._prices);
361
- }
362
-
363
- /**
364
- * Returns claimer bounty, acting as a reward for watchtowers to claim the swap automatically,
365
- * this amount is pre-funded by the user on the destination chain when the swap escrow
366
- * is initiated. For total pre-funded deposit amount see {@link getTotalDeposit}.
367
- */
368
- getClaimerBounty(): TokenAmount<SCToken<T["ChainId"]>, true> {
369
- return toTokenAmount(this._data.getClaimerBounty(), this.wrapper._tokens[this._data.getDepositToken()], this.wrapper._prices);
370
- }
371
-
372
-
373
- //////////////////////////////
374
- //// Bitcoin tx
375
-
376
- /**
377
- * If the required number of confirmations is not known, this function tries to infer it by looping through
378
- * possible confirmation targets and comparing the claim hashes
379
- *
380
- * @param btcTx Bitcoin transaction
381
- * @param vout Output index of the desired output in the bitcoin transaction
382
- *
383
- * @private
384
- */
385
- private inferRequiredConfirmationsCount(btcTx: Omit<BtcTxWithBlockheight, "hex" | "raw">, vout: number): number | undefined {
386
- const txOut = btcTx.outs[vout];
387
- for(let i=1;i<=20;i++) {
388
- const computedClaimHash = this._contract.getHashForOnchain(
389
- Buffer.from(txOut.scriptPubKey.hex, "hex"),
390
- BigInt(txOut.value),
391
- i
392
- );
393
- if(computedClaimHash.toString("hex")===this._data.getClaimHash()) {
394
- return i;
395
- }
396
- }
397
- }
398
-
399
- /**
400
- * @inheritDoc
401
- */
402
- getRequiredConfirmationsCount(): number {
403
- return this.requiredConfirmations ?? NaN;
404
- }
405
-
406
- /**
407
- * Checks whether a bitcoin payment was already made, returns the payment or `null` when no payment has been made.
408
- *
409
- * @internal
410
- */
411
- protected async getBitcoinPayment(): Promise<{
412
- txId: string,
413
- vout: number,
414
- confirmations: number,
415
- targetConfirmations: number,
416
- inputAddresses?: string[]
417
- } | null> {
418
- const txoHashHint = this._data.getTxoHashHint();
419
- if(txoHashHint==null) throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
420
- if(this.address==null) throw new Error("Cannot check bitcoin payment, because the address is not known! This can happen after a swap is recovered.");
421
-
422
- const result = await this.wrapper._btcRpc.checkAddressTxos(this.address, Buffer.from(txoHashHint, "hex"));
423
- if(result==null) return null;
424
-
425
- if(this.requiredConfirmations==null) {
426
- this.requiredConfirmations = this.inferRequiredConfirmationsCount(result.tx, result.vout);
427
- }
428
-
429
- return {
430
- inputAddresses: result.tx.inputAddresses,
431
- txId: result.tx.txid,
432
- vout: result.vout,
433
- confirmations: result.tx.confirmations ?? 0,
434
- targetConfirmations: this.getRequiredConfirmationsCount()
435
- }
436
- }
437
-
438
- /**
439
- * Used to set the txId of the bitcoin payment from the on-chain events listener
440
- *
441
- * @param txId Transaction ID that settled the swap on the smart chain
442
- *
443
- * @internal
444
- */
445
- async _setBitcoinTxId(txId: string) {
446
- if(this.txId!==txId || this.address==null || this.vout==null || this.senderAddress==null || this.amount==null) {
447
- const btcTx = await this.wrapper._btcRpc.getTransaction(txId);
448
- if(btcTx==null) return;
449
-
450
- const txoHashHint = this._data.getTxoHashHint();
451
- if(txoHashHint!=null) {
452
- const expectedTxoHash = Buffer.from(txoHashHint, "hex");
453
- const vout = btcTx.outs.findIndex(out => getTxoHash(out.scriptPubKey.hex, out.value).equals(expectedTxoHash));
454
- if(vout!==-1) {
455
- this.vout = vout;
456
- //If amount or address are not known, parse them from the bitcoin tx
457
- // this can happen if the swap is recovered from on-chain data and
458
- // hence doesn't contain the address and amount data
459
- if(this.amount==null) this.amount = BigInt(btcTx.outs[vout].value);
460
- if(this.address==null) try {
461
- this.address = fromOutputScript(this.wrapper._options.bitcoinNetwork, btcTx.outs[vout].scriptPubKey.hex);
462
- } catch (e: any) {
463
- this.logger.warn("_setBitcoinTxId(): Failed to parse address from output script: ", e);
464
- }
465
- if(this.requiredConfirmations==null) {
466
- this.requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
467
- }
468
- }
469
- }
470
-
471
- if(btcTx.inputAddresses!=null) {
472
- this.senderAddress = btcTx.inputAddresses[0];
473
- }
474
- }
475
-
476
- this.txId = txId;
477
- }
478
-
479
- /**
480
- * @inheritDoc
481
- *
482
- * @throws {Error} if in invalid state (must be {@link FromBTCSwapState.CLAIM_COMMITED})
483
- */
484
- async waitForBitcoinTransaction(
485
- updateCallback?: (txId?: string, confirmations?: number, targetConfirmations?: number, txEtaMs?: number) => void,
486
- checkIntervalSeconds?: number,
487
- abortSignal?: AbortSignal
488
- ): Promise<string> {
489
- if(this._state!==FromBTCSwapState.CLAIM_COMMITED && this._state!==FromBTCSwapState.EXPIRED) throw new Error("Must be in COMMITED state!");
490
- const txoHashHint = this._data.getTxoHashHint();
491
- if(txoHashHint==null) throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
492
- if(this.address==null) throw new Error("Cannot check bitcoin payment, because the address is not known! This can happen after a swap is recovered.");
493
-
494
- let abortedDueToEnoughConfirmationsResult: {
495
- tx: Omit<BtcTxWithBlockheight, "hex" | "raw">, vout: number
496
- } | undefined;
497
- const abortController = extendAbortController(abortSignal);
498
-
499
- const result = await this.wrapper._btcRpc.waitForAddressTxo(
500
- this.address,
501
- Buffer.from(txoHashHint, "hex"),
502
- this.requiredConfirmations ?? 6, //In case confirmation count is not known, we use a conservative estimate
503
- (btcTx?: Omit<BtcTxWithBlockheight, "hex" | "raw">, vout?: number, txEtaMs?: number) => {
504
- let requiredConfirmations = this.requiredConfirmations;
505
-
506
- if(btcTx!=null && vout!=null && requiredConfirmations==null) {
507
- requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
508
- }
509
-
510
- if(btcTx!=null && (btcTx.txid!==this.txId || (this.requiredConfirmations==null && requiredConfirmations!=null))) {
511
- this.txId = btcTx.txid;
512
- this.vout = vout;
513
- this.requiredConfirmations = requiredConfirmations;
514
- if(btcTx.inputAddresses!=null) this.senderAddress = btcTx.inputAddresses[0];
515
- this._saveAndEmit().catch(e => {
516
- this.logger.error("waitForBitcoinTransaction(): Failed to save swap from within waitForAddressTxo callback:", e)
517
- });
518
- }
519
-
520
- //Abort the loop as soon as the transaction gets enough confirmations, this is required in case
521
- // we pass a default 6 confirmations to the fn, but then are able to infer the actual confirmation
522
- // target from the prior block
523
- if(btcTx?.confirmations!=null && requiredConfirmations!=null && requiredConfirmations<=btcTx.confirmations && vout!=null) {
524
- abortedDueToEnoughConfirmationsResult = {
525
- tx: btcTx,
526
- vout
527
- };
528
- abortController.abort();
529
- return;
530
- }
531
-
532
- if(updateCallback!=null) updateCallback(btcTx?.txid, btcTx==null ? undefined : (btcTx?.confirmations ?? 0), requiredConfirmations ?? NaN, txEtaMs);
533
- },
534
- abortController.signal,
535
- checkIntervalSeconds
536
- ).catch(e => {
537
- //We catch the case when the loop was aborted due to the transaction getting enough confirmations
538
- if(abortedDueToEnoughConfirmationsResult!=null) return abortedDueToEnoughConfirmationsResult;
539
- throw e;
540
- });
541
-
542
- if(abortSignal!=null) abortSignal.throwIfAborted();
543
-
544
- this.txId = result.tx.txid;
545
- this.vout = result.vout;
546
- if(result.tx.inputAddresses!=null) this.senderAddress = result.tx.inputAddresses[0];
547
-
548
- if(
549
- (this._state as FromBTCSwapState)!==FromBTCSwapState.CLAIM_CLAIMED &&
550
- (this._state as FromBTCSwapState)!==FromBTCSwapState.FAILED
551
- ) {
552
- this.btcTxConfirmedAt ??= Date.now();
553
- this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
554
- }
555
-
556
- await this._saveAndEmit();
557
-
558
- return result.tx.txid;
559
- }
560
-
561
- /**
562
- * Private getter of the funded PSBT that doesn't check current state
563
- *
564
- * @param _bitcoinWallet Bitcoin wallet to fund the PSBT with
565
- * @param feeRate Optional bitcoin fee rate in sats/vB
566
- * @param additionalOutputs Optional additional outputs that should also be included in the generated PSBT
567
- *
568
- * @private
569
- */
570
- private async _getFundedPsbt(
571
- _bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface,
572
- feeRate?: number,
573
- additionalOutputs?: ({amount: bigint, outputScript: Uint8Array} | {amount: bigint, address: string})[]
574
- ): Promise<{psbt: Transaction, psbtHex: string, psbtBase64: string, signInputs: number[]}> {
575
- if(this.address==null) throw new Error("Cannot create funded PSBT, because the address is not known! This can happen after a swap is recovered.");
576
-
577
- let bitcoinWallet: IBitcoinWallet;
578
- if(isIBitcoinWallet(_bitcoinWallet)) {
579
- bitcoinWallet = _bitcoinWallet;
580
- } else {
581
- bitcoinWallet = new SingleAddressBitcoinWallet(this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork, _bitcoinWallet);
582
- }
583
- //TODO: Maybe re-introduce fee rate check here if passed from the user
584
- if(feeRate==null) {
585
- feeRate = await bitcoinWallet.getFeeRate();
586
- }
587
-
588
- const basePsbt = new Transaction({
589
- allowUnknownOutputs: true,
590
- allowLegacyWitnessUtxo: true
591
- });
592
- basePsbt.addOutput({
593
- amount: this.amount,
594
- script: toOutputScript(this.wrapper._options.bitcoinNetwork, this.address)
595
- });
596
- if(additionalOutputs!=null) additionalOutputs.forEach(output => {
597
- basePsbt.addOutput({
598
- amount: output.amount,
599
- script: (output as {outputScript: Uint8Array}).outputScript ?? toOutputScript(this.wrapper._options.bitcoinNetwork, (output as {address: string}).address)
600
- });
601
- });
602
-
603
- const psbt = await bitcoinWallet.fundPsbt(basePsbt, feeRate);
604
- //Sign every input
605
- const signInputs: number[] = [];
606
- for(let i=0;i<psbt.inputsLength;i++) {
607
- signInputs.push(i);
608
- }
609
- const serializedPsbt = Buffer.from(psbt.toPSBT());
610
- return {
611
- psbt,
612
- psbtHex: serializedPsbt.toString("hex"),
613
- psbtBase64: serializedPsbt.toString("base64"),
614
- signInputs
615
- };
616
- }
617
-
618
- /**
619
- * @inheritDoc
620
- */
621
- getFundedPsbt(
622
- _bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface,
623
- feeRate?: number,
624
- additionalOutputs?: ({amount: bigint, outputScript: Uint8Array} | {amount: bigint, address: string})[]
625
- ) {
626
- if(this._state!==FromBTCSwapState.CLAIM_COMMITED)
627
- throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
628
- return this._getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs);
629
- }
630
-
631
- /**
632
- * @inheritDoc
633
- *
634
- * @throws {Error} if the swap is in invalid state (not in {@link FromBTCSwapState.CLAIM_COMMITED}), or if
635
- * the swap bitcoin address already expired.
636
- */
637
- async submitPsbt(_psbt: Transaction | string): Promise<string> {
638
- const psbt = parsePsbtTransaction(_psbt);
639
- if(this._state!==FromBTCSwapState.CLAIM_COMMITED)
640
- throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
641
-
642
- //Ensure not expired
643
- if(this.getTimeoutTime()<Date.now()) {
644
- throw new Error("Swap address expired!");
645
- }
646
-
647
- const output0 = psbt.getOutput(0);
648
- if(this.amount!=null && output0.amount!==this.amount)
649
- throw new Error("PSBT output amount invalid, expected: "+this.amount+" got: "+output0.amount);
650
- if(this.address!=null) {
651
- const expectedOutputScript = toOutputScript(this.wrapper._options.bitcoinNetwork, this.address);
652
- if(output0.script==null || !expectedOutputScript.equals(output0.script))
653
- throw new Error("PSBT output script invalid!");
654
- }
655
-
656
- if(!psbt.isFinal) psbt.finalize();
657
-
658
- return await this.wrapper._btcRpc.sendRawTransaction(Buffer.from(psbt.toBytes(true, true)).toString("hex"));
659
- }
660
-
661
- /**
662
- * @inheritDoc
663
- */
664
- async estimateBitcoinFee(_bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface, feeRate?: number): Promise<TokenAmount<BtcToken<false>, true> | null> {
665
- if(this.address==null || this.amount==null) return null;
666
- const bitcoinWallet: IBitcoinWallet = toBitcoinWallet(_bitcoinWallet, this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork);
667
- const txFee = await bitcoinWallet.getTransactionFee(this.address, this.amount, feeRate);
668
- if(txFee==null) return null;
669
- return toTokenAmount(BigInt(txFee), BitcoinTokens.BTC, this.wrapper._prices);
670
- }
671
-
672
- /**
673
- * @inheritDoc
674
- */
675
- async sendBitcoinTransaction(wallet: IBitcoinWallet | MinimalBitcoinWalletInterfaceWithSigner, feeRate?: number): Promise<string> {
676
- if(this.address==null || this.amount==null) throw new Error("Cannot send bitcoin transaction, because the address is not known! This can happen after a swap is recovered.");
677
-
678
- if(this._state!==FromBTCSwapState.CLAIM_COMMITED)
679
- throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
680
-
681
- //Ensure not expired
682
- if(this.getTimeoutTime()<Date.now()) {
683
- throw new Error("Swap address expired!");
684
- }
685
-
686
- if(isIBitcoinWallet(wallet)) {
687
- return await wallet.sendTransaction(this.address, this.amount, feeRate);
688
- } else {
689
- const {psbt, psbtHex, psbtBase64, signInputs} = await this.getFundedPsbt(wallet, feeRate);
690
- const signedPsbt = await wallet.signPsbt({
691
- psbt, psbtHex, psbtBase64
692
- }, signInputs);
693
- return await this.submitPsbt(signedPsbt);
694
- }
695
- }
696
-
697
-
698
- //////////////////////////////
699
- //// Execution
700
-
701
- /**
702
- * Executes the swap with the provided bitcoin wallet,
703
- *
704
- * @param dstSigner Signer on the destination network, needs to have the same address as the one specified when
705
- * quote was created, this is required for legacy swaps because the destination wallet needs to actively open
706
- * a bitcoin swap address to which the BTC is then sent, this means that the address also needs to have enough
707
- * native tokens to pay for gas on the destination network
708
- * @param wallet Bitcoin wallet to use to sign the bitcoin transaction, can also be null - then the execution waits
709
- * till a transaction is received from an external wallet
710
- * @param callbacks Callbacks to track the progress of the swap
711
- * @param options Optional options for the swap like feeRate, AbortSignal, and timeouts/intervals
712
- *
713
- * @returns {boolean} Whether a swap was settled automatically by swap watchtowers or requires manual claim by the
714
- * user, in case `false` is returned the user should call `swap.claim()` to settle the swap on the destination manually
715
- */
716
- async execute(
717
- dstSigner: T["Signer"] | T["NativeSigner"],
718
- wallet?: IBitcoinWallet | MinimalBitcoinWalletInterfaceWithSigner | null | undefined,
719
- callbacks?: {
720
- onDestinationCommitSent?: (destinationCommitTxId: string) => void,
721
- onSourceTransactionSent?: (sourceTxId: string) => void,
722
- onSourceTransactionConfirmationStatus?: (sourceTxId?: string, confirmations?: number, targetConfirations?: number, etaMs?: number) => void,
723
- onSourceTransactionConfirmed?: (sourceTxId: string) => void,
724
- onSwapSettled?: (destinationTxId: string) => void
725
- },
726
- options?: {
727
- feeRate?: number,
728
- abortSignal?: AbortSignal,
729
- btcTxCheckIntervalSeconds?: number,
730
- maxWaitTillAutomaticSettlementSeconds?: number
731
- }
732
- ): Promise<boolean> {
733
- if(this._state===FromBTCSwapState.FAILED) throw new Error("Swap failed!");
734
- if(this._state===FromBTCSwapState.EXPIRED) throw new Error("Swap address expired!");
735
- if(this._state===FromBTCSwapState.QUOTE_EXPIRED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) throw new Error("Swap quote expired!");
736
- if(this._state===FromBTCSwapState.CLAIM_CLAIMED) throw new Error("Swap already settled!");
737
-
738
- if(this._state===FromBTCSwapState.PR_CREATED) {
739
- await this.commit(dstSigner, options?.abortSignal, undefined, callbacks?.onDestinationCommitSent);
740
- }
741
- if(this._state===FromBTCSwapState.CLAIM_COMMITED) {
742
- if(wallet!=null) {
743
- const bitcoinPaymentSent = await this.getBitcoinPayment();
744
-
745
- if(bitcoinPaymentSent==null) {
746
- //Send btc tx
747
- const txId = await this.sendBitcoinTransaction(wallet, options?.feeRate);
748
- if(callbacks?.onSourceTransactionSent!=null) callbacks.onSourceTransactionSent(txId);
749
- }
750
- }
751
-
752
- const txId = await this.waitForBitcoinTransaction(callbacks?.onSourceTransactionConfirmationStatus, options?.btcTxCheckIntervalSeconds, options?.abortSignal);
753
- if (callbacks?.onSourceTransactionConfirmed != null) callbacks.onSourceTransactionConfirmed(txId);
754
- }
755
-
756
- // @ts-ignore
757
- if(this._state===FromBTCSwapState.CLAIM_CLAIMED) return true;
758
-
759
- if(this._state===FromBTCSwapState.BTC_TX_CONFIRMED) {
760
- const success = await this.waitTillClaimed(options?.maxWaitTillAutomaticSettlementSeconds ?? 60, options?.abortSignal);
761
- if(success && callbacks?.onSwapSettled!=null) callbacks.onSwapSettled(this.getOutputTxId()!);
762
- return success;
763
- }
764
-
765
- throw new Error("Invalid state reached!");
766
- }
767
-
768
- /**
769
- * @inheritDoc
770
- *
771
- * @param options.bitcoinFeeRate Optional fee rate to use for the created Bitcoin transaction
772
- * @param options.bitcoinWallet Bitcoin wallet to use, when provided the function returns a funded
773
- * psbt (`"FUNDED_PSBT"`), if not passed just a bitcoin receive address is returned (`"ADDRESS"`)
774
- * @param options.skipChecks Skip checks like making sure init signature is still valid and swap
775
- * wasn't commited yet (this is handled on swap creation, if you commit right after quoting, you
776
- * can use `skipChecks=true`)
777
- *
778
- * @throws {Error} if the swap or quote is expired, or if triggered in invalid state
779
- */
780
- async txsExecute(options?: {
781
- bitcoinFeeRate?: number,
782
- bitcoinWallet?: MinimalBitcoinWalletInterface,
783
- skipChecks?: boolean
784
- }) {
785
- if(this._state===FromBTCSwapState.PR_CREATED) {
786
- if(!await this._verifyQuoteValid()) throw new Error("Quote already expired or close to expiry!");
787
- if(this.getTimeoutTime()<Date.now()) throw new Error("Swap address already expired or close to expiry!");
788
- return [
789
- {
790
- name: "Commit" as const,
791
- description: `Opens up the bitcoin swap address on the ${this.chainIdentifier} side`,
792
- chain: this.chainIdentifier,
793
- txs: await this.txsCommit(options?.skipChecks)
794
- },
795
- {
796
- name: "Payment" as const,
797
- description: "Send funds to the bitcoin swap address",
798
- chain: "BITCOIN" as const,
799
- txs: [
800
- options?.bitcoinWallet==null ? {
801
- address: this.address ?? "",
802
- amount: Number(this.amount),
803
- hyperlink: this._getHyperlink(),
804
- type: "ADDRESS" as const
805
- } : {
806
- ...await this.getFundedPsbt(options.bitcoinWallet),
807
- type: "FUNDED_PSBT" as const
808
- }
809
- ]
810
- }
811
- ];
812
- }
813
-
814
- if(this._state===FromBTCSwapState.CLAIM_COMMITED) {
815
- if(this.getTimeoutTime()<Date.now()) throw new Error("Swap address already expired or close to expiry!");
816
- return [
817
- {
818
- name: "Payment" as const,
819
- description: "Send funds to the bitcoin swap address",
820
- chain: "BITCOIN" as const,
821
- txs: [
822
- options?.bitcoinWallet==null ? {
823
- address: this.getAddress(),
824
- amount: Number(this.amount),
825
- hyperlink: this._getHyperlink(),
826
- type: "ADDRESS" as const
827
- } : {
828
- ...await this.getFundedPsbt(options.bitcoinWallet),
829
- type: "FUNDED_PSBT" as const
830
- }
831
- ]
832
- }
833
- ];
834
- }
835
-
836
- throw new Error("Invalid swap state to obtain execution txns, required PR_CREATED or CLAIM_COMMITED");
837
- }
838
-
839
- /**
840
- * @inheritDoc
841
- *
842
- * @param options.bitcoinFeeRate Optional fee rate to use for the created Bitcoin transaction
843
- * @param options.bitcoinWallet Bitcoin wallet to use, when provided the function returns a funded
844
- * psbt (`"FUNDED_PSBT"`), if not passed just a bitcoin receive address is returned (`"ADDRESS"`)
845
- * @param options.skipChecks Skip checks like making sure init signature is still valid and swap
846
- * wasn't commited yet (this is handled on swap creation, if you commit right after quoting, you
847
- * can use `skipChecks=true`)
848
- * @param options.manualSettlementSmartChainSigner Optional smart chain signer to create a manual claim (settlement) transaction
849
- * @param options.maxWaitTillAutomaticSettlementSeconds Maximum time to wait for an automatic settlement after
850
- * the bitcoin transaction is confirmed (defaults to 60 seconds)
851
- */
852
- async getCurrentActions(options?: {
853
- bitcoinFeeRate?: number,
854
- bitcoinWallet?: MinimalBitcoinWalletInterface,
855
- skipChecks?: boolean,
856
- manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
857
- maxWaitTillAutomaticSettlementSeconds?: number
858
- }): Promise<SwapExecutionAction<T>[]> {
859
- if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.CLAIM_COMMITED) {
860
- try {
861
- return await this.txsExecute(options);
862
- } catch (e) {}
863
- }
864
- if(this.isClaimable()) {
865
- if(
866
- this.btcTxConfirmedAt==null ||
867
- options?.maxWaitTillAutomaticSettlementSeconds===0 ||
868
- (Date.now() - this.btcTxConfirmedAt) > (options?.maxWaitTillAutomaticSettlementSeconds ?? 60)*1000
869
- ) {
870
- return [{
871
- name: "Claim" as const,
872
- description: "Manually settle (claim) the swap on the destination smart chain",
873
- chain: this.chainIdentifier,
874
- txs: await this.txsClaim(options?.manualSettlementSmartChainSigner)
875
- }];
876
- }
877
- }
878
- return[];
879
- }
880
-
881
- //////////////////////////////
882
- //// Commit
883
-
884
- /**
885
- * @inheritDoc
886
- *
887
- * @throws {Error} If invalid signer is provided that doesn't match the swap data
888
- */
889
- async commit(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, skipChecks?: boolean, onBeforeTxSent?: (txId: string) => void): Promise<string> {
890
- const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
891
- this.checkSigner(signer);
892
- let txCount = 0;
893
- const txs = await this.txsCommit(skipChecks);
894
- const result = await this.wrapper._chain.sendAndConfirm(
895
- signer, txs, true, abortSignal, undefined, (txId: string) => {
896
- txCount++;
897
- if(onBeforeTxSent!=null && txCount===txs.length) onBeforeTxSent(txId);
898
- return Promise.resolve();
899
- }
900
- );
901
-
902
- this._commitTxId = result[result.length - 1];
903
- if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED || this._state===FromBTCSwapState.QUOTE_EXPIRED) {
904
- await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
905
- }
906
- return this._commitTxId;
907
- }
908
-
909
- /**
910
- * @inheritDoc
911
- */
912
- async waitTillCommited(abortSignal?: AbortSignal): Promise<void> {
913
- if(this._state===FromBTCSwapState.CLAIM_COMMITED || this._state===FromBTCSwapState.CLAIM_CLAIMED) return Promise.resolve();
914
- if(this._state!==FromBTCSwapState.PR_CREATED && this._state!==FromBTCSwapState.QUOTE_SOFT_EXPIRED) throw new Error("Invalid state");
915
-
916
- const abortController = extendAbortController(abortSignal);
917
- const result = await Promise.race([
918
- this.watchdogWaitTillCommited(undefined, abortController.signal),
919
- this.waitTillState(FromBTCSwapState.CLAIM_COMMITED, "gte", abortController.signal).then(() => 0)
920
- ]);
921
- abortController.abort();
922
-
923
- if(result===0) this.logger.debug("waitTillCommited(): Resolved from state changed");
924
- if(result===true) this.logger.debug("waitTillCommited(): Resolved from watchdog - commited");
925
- if(result===false) {
926
- this.logger.debug("waitTillCommited(): Resolved from watchdog - signature expired");
927
- if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
928
- await this._saveAndEmit(FromBTCSwapState.QUOTE_EXPIRED);
929
- }
930
- return;
931
- }
932
-
933
- if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
934
- await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
935
- }
936
- }
937
-
938
-
939
- //////////////////////////////
940
- //// Claim
941
-
942
- /**
943
- * Returns transactions for settling (claiming) the swap if the swap requires manual settlement, you can check so
944
- * with isClaimable. After sending the transaction manually be sure to call the waitTillClaimed function to wait
945
- * till the claim transaction is observed, processed by the SDK and state of the swap properly updated.
946
- *
947
- * @remarks
948
- * Might also return transactions necessary to sync the bitcoin light client.
949
- *
950
- * @param _signer Address of the signer to create the claim transactions for
951
- *
952
- * @throws {Error} If the swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
953
- */
954
- async txsClaim(_signer?: string | T["Signer"] | T["NativeSigner"]): Promise<T["TX"][]> {
955
- let signer: string | T["Signer"] | undefined = undefined;
956
- if(_signer!=null) {
957
- if (typeof (_signer) === "string") {
958
- signer = _signer;
959
- } else if (isAbstractSigner(_signer)) {
960
- signer = _signer;
961
- } else {
962
- signer = await this.wrapper._chain.wrapSigner(_signer);
963
- }
964
- }
965
-
966
- if(this._state!==FromBTCSwapState.BTC_TX_CONFIRMED) throw new Error("Must be in BTC_TX_CONFIRMED state!");
967
- if(this.txId==null || this.vout==null) throw new Error("Bitcoin transaction ID not known!");
968
-
969
- const tx = await this.wrapper._btcRpc.getTransaction(this.txId);
970
- if(tx==null) throw new Error("Bitcoin transaction not found on the network!");
971
-
972
- this.requiredConfirmations ??= this.inferRequiredConfirmationsCount(tx, this.vout);
973
- if(this.requiredConfirmations==null)
974
- throw new Error("Cannot create claim transaction, because required confirmations are not known and cannot be infered! This can happen after a swap is recovered.");
975
-
976
- if(tx.blockhash==null || tx.confirmations==null || tx.blockheight==null || tx.confirmations<this.requiredConfirmations)
977
- throw new Error("Bitcoin transaction not confirmed yet!");
978
-
979
- return await this._contract.txsClaimWithTxData(signer ?? this._getInitiator(), this._data, {
980
- blockhash: tx.blockhash,
981
- confirmations: tx.confirmations,
982
- txid: tx.txid,
983
- hex: tx.hex,
984
- height: tx.blockheight
985
- }, this.requiredConfirmations, this.vout, undefined, this.wrapper._synchronizer(this._contractVersion), true);
986
- }
987
-
988
- /**
989
- * Settles the swap by claiming the funds on the destination chain if the swap requires manual settlement, you can
990
- * check so with isClaimable.
991
- *
992
- * @remarks
993
- * Might also sync the bitcoin light client during the process.
994
- *
995
- * @param _signer Signer to use for signing the settlement transactions, can also be different to the recipient
996
- * @param abortSignal Abort signal
997
- * @param onBeforeTxSent Optional callback triggered before the claim transaction is broadcasted
998
- *
999
- * @returns Transaction ID of the settlement (claim) transaction on the destination smart chain
1000
- */
1001
- async claim(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, onBeforeTxSent?: (txId: string) => void): Promise<string> {
1002
- const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
1003
- let txIds: string[];
1004
- try {
1005
- let txCount = 0;
1006
- const txs = await this.txsClaim(signer);
1007
- txIds = await this.wrapper._chain.sendAndConfirm(
1008
- signer, txs, true, abortSignal, undefined, (txId: string) => {
1009
- txCount++;
1010
- if(onBeforeTxSent!=null && txCount===txs.length) onBeforeTxSent(txId);
1011
- return Promise.resolve();
1012
- }
1013
- );
1014
- } catch (e) {
1015
- this.logger.info("claim(): Failed to claim ourselves, checking swap claim state...");
1016
- if(this._state===FromBTCSwapState.CLAIM_CLAIMED) {
1017
- this.logger.info("claim(): Transaction state is CLAIM_CLAIMED, swap was successfully claimed by the watchtower");
1018
- return this._claimTxId!;
1019
- }
1020
- const status = await this._contract.getCommitStatus(this._getInitiator(), this._data);
1021
- if(status?.type===SwapCommitStateType.PAID) {
1022
- this.logger.info("claim(): Transaction commit status is PAID, swap was successfully claimed by the watchtower");
1023
- if(this._claimTxId==null) this._claimTxId = await status.getClaimTxId();
1024
- const txId = Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
1025
- await this._setBitcoinTxId(txId);
1026
- await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
1027
- return this._claimTxId;
1028
- }
1029
- throw e;
1030
- }
1031
-
1032
- this._claimTxId = txIds[txIds.length - 1];
1033
- if(
1034
- this._state===FromBTCSwapState.CLAIM_COMMITED || this._state===FromBTCSwapState.BTC_TX_CONFIRMED ||
1035
- this._state===FromBTCSwapState.EXPIRED || this._state===FromBTCSwapState.FAILED
1036
- ) {
1037
- await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
1038
- }
1039
- return txIds[txIds.length - 1];
1040
- }
1041
-
1042
- /**
1043
- * @inheritDoc
1044
- *
1045
- * @throws {Error} If swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
1046
- * @throws {Error} If the LP refunded sooner than we were able to claim
1047
- */
1048
- async waitTillClaimed(maxWaitTimeSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
1049
- if(this._state===FromBTCSwapState.CLAIM_CLAIMED) return Promise.resolve(true);
1050
- if(this._state!==FromBTCSwapState.BTC_TX_CONFIRMED) throw new Error("Invalid state (not BTC_TX_CONFIRMED)");
1051
-
1052
- const abortController = extendAbortController(abortSignal);
1053
-
1054
- let timedOut: boolean = false;
1055
- if(maxWaitTimeSeconds!=null) {
1056
- const timeout = setTimeout(() => {
1057
- timedOut = true;
1058
- abortController.abort();
1059
- }, maxWaitTimeSeconds * 1000);
1060
- abortController.signal.addEventListener("abort", () => clearTimeout(timeout));
1061
- }
1062
-
1063
- let res: 0 | 1 | SwapCommitState;
1064
- try {
1065
- res = await Promise.race([
1066
- this.watchdogWaitTillResult(undefined, abortController.signal),
1067
- this.waitTillState(FromBTCSwapState.CLAIM_CLAIMED, "eq", abortController.signal).then(() => 0 as const),
1068
- this.waitTillState(FromBTCSwapState.FAILED, "eq", abortController.signal).then(() => 1 as const),
1069
- ]);
1070
- abortController.abort();
1071
- } catch (e) {
1072
- abortController.abort();
1073
- if(timedOut) return false;
1074
- throw e;
1075
- }
1076
-
1077
- if(res===0) {
1078
- this.logger.debug("waitTillClaimed(): Resolved from state change (CLAIM_CLAIMED)");
1079
- return true;
1080
- }
1081
- if(res===1) {
1082
- this.logger.debug("waitTillClaimed(): Resolved from state change (FAILED)");
1083
- throw new Error("Offerer refunded during claiming");
1084
- }
1085
- this.logger.debug("waitTillClaimed(): Resolved from watchdog");
1086
-
1087
- if(res?.type===SwapCommitStateType.PAID) {
1088
- if((this._state as FromBTCSwapState)!==FromBTCSwapState.CLAIM_CLAIMED) {
1089
- if(this._claimTxId==null) this._claimTxId = await res.getClaimTxId();
1090
- const txId = Buffer.from(await res.getClaimResult(), "hex").reverse().toString("hex");
1091
- await this._setBitcoinTxId(txId);
1092
- await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
1093
- }
1094
- }
1095
- if(res?.type===SwapCommitStateType.NOT_COMMITED || res?.type===SwapCommitStateType.EXPIRED) {
1096
- if(
1097
- (this._state as FromBTCSwapState)!==FromBTCSwapState.CLAIM_CLAIMED &&
1098
- (this._state as FromBTCSwapState)!==FromBTCSwapState.FAILED
1099
- ) {
1100
- if(res.getRefundTxId!=null) this._refundTxId = await res.getRefundTxId();
1101
- await this._saveAndEmit(FromBTCSwapState.FAILED);
1102
- }
1103
- throw new Error("Swap expired while waiting for claim!");
1104
- }
1105
-
1106
- return true;
1107
- }
1108
-
1109
-
1110
- //////////////////////////////
1111
- //// Storage
1112
-
1113
- /**
1114
- * @inheritDoc
1115
- */
1116
- serialize(): any {
1117
- return {
1118
- ...super.serialize(),
1119
- address: this.address,
1120
- amount: this.amount==null ? null: this.amount.toString(10),
1121
- requiredConfirmations: this.requiredConfirmations,
1122
- senderAddress: this.senderAddress,
1123
- txId: this.txId,
1124
- vout: this.vout,
1125
- btcTxConfirmedAt: this.btcTxConfirmedAt
1126
- };
1127
- }
1128
-
1129
-
1130
- //////////////////////////////
1131
- //// Swap ticks & sync
1132
-
1133
- /**
1134
- * Checks the swap's state on-chain and compares it to its internal state, updates/changes it according to on-chain
1135
- * data
1136
- *
1137
- * @private
1138
- */
1139
- private async syncStateFromChain(quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState): Promise<boolean> {
1140
- if(
1141
- this._state===FromBTCSwapState.PR_CREATED ||
1142
- this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
1143
- this._state===FromBTCSwapState.CLAIM_COMMITED ||
1144
- this._state===FromBTCSwapState.BTC_TX_CONFIRMED ||
1145
- this._state===FromBTCSwapState.EXPIRED
1146
- ) {
1147
- let quoteExpired: boolean = false;
1148
- if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
1149
- quoteExpired = quoteDefinitelyExpired ?? await this._verifyQuoteDefinitelyExpired(); //Make sure we check for expiry here, to prevent race conditions
1150
- }
1151
-
1152
- const status = commitStatus ?? await this._contract.getCommitStatus(this._getInitiator(), this._data);
1153
- if(status!=null && await this._forciblySetOnchainState(status)) return true;
1154
-
1155
- if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
1156
- if(quoteExpired) {
1157
- this._state = FromBTCSwapState.QUOTE_EXPIRED;
1158
- return true;
1159
- }
1160
- }
1161
- }
1162
-
1163
- return false;
1164
- }
1165
-
1166
- /**
1167
- * @inheritDoc
1168
- * @internal
1169
- */
1170
- _shouldFetchOnchainState(): boolean {
1171
- return this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
1172
- this._state===FromBTCSwapState.CLAIM_COMMITED || this._state===FromBTCSwapState.BTC_TX_CONFIRMED ||
1173
- this._state===FromBTCSwapState.EXPIRED;
1174
- }
1175
-
1176
- /**
1177
- * @inheritDoc
1178
- * @internal
1179
- */
1180
- _shouldFetchExpiryStatus(): boolean {
1181
- return this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED;
1182
- }
1183
-
1184
- /**
1185
- * @inheritDoc
1186
- * @internal
1187
- */
1188
- async _sync(save?: boolean, quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState): Promise<boolean> {
1189
- const changed = await this.syncStateFromChain(quoteDefinitelyExpired, commitStatus);
1190
- if(changed && save) await this._saveAndEmit();
1191
- return changed;
1192
- }
1193
-
1194
- private btcTxLastChecked?: number;
1195
-
1196
- /**
1197
- * @inheritDoc
1198
- * @internal
1199
- */
1200
- async _forciblySetOnchainState(status: SwapCommitState): Promise<boolean> {
1201
- switch(status.type) {
1202
- case SwapCommitStateType.PAID:
1203
- if(this._claimTxId==null) this._claimTxId = await status.getClaimTxId();
1204
- const txId = Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
1205
- await this._setBitcoinTxId(txId);
1206
- this._state = FromBTCSwapState.CLAIM_CLAIMED;
1207
- return true;
1208
- case SwapCommitStateType.NOT_COMMITED:
1209
- if(this._refundTxId==null && status.getRefundTxId) this._refundTxId = await status.getRefundTxId();
1210
- if(this._refundTxId!=null) {
1211
- this._state = FromBTCSwapState.FAILED;
1212
- return true;
1213
- }
1214
- break;
1215
- case SwapCommitStateType.EXPIRED:
1216
- if(this._refundTxId==null && status.getRefundTxId) this._refundTxId = await status.getRefundTxId();
1217
- this._state = this._refundTxId==null ? FromBTCSwapState.QUOTE_EXPIRED : FromBTCSwapState.FAILED;
1218
- return true;
1219
- case SwapCommitStateType.COMMITED:
1220
- let save: boolean = false;
1221
- if(this._state!==FromBTCSwapState.CLAIM_COMMITED && this._state!==FromBTCSwapState.BTC_TX_CONFIRMED && this._state!==FromBTCSwapState.EXPIRED) {
1222
- this._state = FromBTCSwapState.CLAIM_COMMITED;
1223
- save = true;
1224
- }
1225
- if(this.address==null) return save;
1226
-
1227
- this.btcTxLastChecked = Date.now();
1228
- const res = await this.getBitcoinPayment();
1229
- if(res!=null) {
1230
- if(this.txId!==res.txId) {
1231
- if(res.inputAddresses!=null) this.senderAddress = res.inputAddresses[0];
1232
- this.txId = res.txId;
1233
- this.vout = res.vout;
1234
- save = true;
1235
- }
1236
- if(this.requiredConfirmations!=null && res.confirmations>=this.requiredConfirmations) {
1237
- this.btcTxConfirmedAt ??= Date.now();
1238
- this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
1239
- save = true;
1240
- }
1241
- }
1242
- return save;
1243
- }
1244
- return false;
1245
- }
1246
-
1247
- /**
1248
- * @inheritDoc
1249
- * @internal
1250
- */
1251
- async _tick(save?: boolean): Promise<boolean> {
1252
- switch(this._state) {
1253
- case FromBTCSwapState.PR_CREATED:
1254
- if(this.expiry<Date.now()) {
1255
- this._state = FromBTCSwapState.QUOTE_SOFT_EXPIRED;
1256
- if(save) await this._saveAndEmit();
1257
- return true;
1258
- }
1259
- break;
1260
- case FromBTCSwapState.CLAIM_COMMITED:
1261
- if(this.getTimeoutTime()<Date.now()) {
1262
- this._state = FromBTCSwapState.EXPIRED;
1263
- if(save) await this._saveAndEmit();
1264
- return true;
1265
- }
1266
- case FromBTCSwapState.EXPIRED:
1267
- //Check if bitcoin payment was received at least every 2 minutes
1268
- if(this.btcTxLastChecked==null || Date.now() - this.btcTxLastChecked > 120_000) {
1269
- if(this.address!=null) try {
1270
- this.btcTxLastChecked = Date.now();
1271
- const res = await this.getBitcoinPayment();
1272
- if(res!=null) {
1273
- let shouldSave: boolean = false;
1274
- if(this.txId!==res.txId) {
1275
- this.txId = res.txId;
1276
- this.vout = res.vout;
1277
- if(res.inputAddresses!=null) this.senderAddress = res.inputAddresses[0];
1278
- shouldSave = true;
1279
- }
1280
- if(this.requiredConfirmations!=null && res.confirmations>=this.requiredConfirmations) {
1281
- this.btcTxConfirmedAt ??= Date.now();
1282
- this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
1283
- if(save) await this._saveAndEmit();
1284
- shouldSave = true;
1285
- }
1286
- if(shouldSave && save) await this._saveAndEmit();
1287
- return shouldSave;
1288
- }
1289
- } catch (e) {
1290
- this.logger.warn("tickSwap("+this.getIdentifierHashString()+"): ", e);
1291
- }
1292
- }
1293
- break;
1294
- }
1295
-
1296
- return false;
1297
- }
1298
-
1299
- }
1
+ import {IFromBTCSelfInitSwap} from "../IFromBTCSelfInitSwap";
2
+ import {SwapType} from "../../../../enums/SwapType";
3
+ import {FromBTCDefinition, FromBTCWrapper} from "./FromBTCWrapper";
4
+ import {
5
+ BtcTxWithBlockheight,
6
+ ChainType,
7
+ isAbstractSigner,
8
+ SwapCommitState,
9
+ SwapCommitStateType,
10
+ SwapData
11
+ } from "@atomiqlabs/base";
12
+ import {Buffer} from "buffer";
13
+ import {
14
+ extendAbortController,
15
+ getTxoHash, toBigInt
16
+ } from "../../../../utils/Utils";
17
+ import {
18
+ fromOutputScript,
19
+ parsePsbtTransaction,
20
+ toOutputScript,
21
+ } from "../../../../utils/BitcoinUtils";
22
+ import {IBitcoinWallet, isIBitcoinWallet} from "../../../../bitcoin/wallet/IBitcoinWallet";
23
+ import {IBTCWalletSwap} from "../../../IBTCWalletSwap";
24
+ import {Transaction} from "@scure/btc-signer";
25
+ import {SingleAddressBitcoinWallet} from "../../../../bitcoin/wallet/SingleAddressBitcoinWallet";
26
+ import {
27
+ MinimalBitcoinWalletInterface,
28
+ MinimalBitcoinWalletInterfaceWithSigner
29
+ } from "../../../../types/wallets/MinimalBitcoinWalletInterface";
30
+ import {IClaimableSwap} from "../../../IClaimableSwap";
31
+ import {IEscrowSelfInitSwapInit, isIEscrowSelfInitSwapInit} from "../../IEscrowSelfInitSwap";
32
+ import {IAddressSwap} from "../../../IAddressSwap";
33
+ import {TokenAmount, toTokenAmount} from "../../../../types/TokenAmount";
34
+ import {BitcoinTokens, BtcToken, SCToken} from "../../../../types/Token";
35
+ import {getLogger, LoggerType} from "../../../../utils/Logger";
36
+ import {toBitcoinWallet} from "../../../../utils/BitcoinWalletUtils";
37
+ import {SwapExecutionAction} from "../../../../types/SwapExecutionAction";
38
+
39
+ /**
40
+ * State enum for legacy escrow based Bitcoin -> Smart chain swaps.
41
+ *
42
+ * @category Swaps/Legacy/Bitcoin → Smart chain
43
+ */
44
+ export enum FromBTCSwapState {
45
+ /**
46
+ * Bitcoin swap address has expired and the intermediary (LP) has already refunded
47
+ * its funds. No BTC should be sent anymore!
48
+ */
49
+ FAILED = -4,
50
+ /**
51
+ * Bitcoin swap address has expired, user should not send any BTC anymore! Though
52
+ * the intermediary (LP) hasn't refunded yet. So if there is a transaction already
53
+ * in-flight the swap might still succeed.
54
+ */
55
+ EXPIRED = -3,
56
+ /**
57
+ * Swap has expired for good and there is no way how it can be executed anymore
58
+ */
59
+ QUOTE_EXPIRED = -2,
60
+ /**
61
+ * A swap is almost expired, and it should be presented to the user as expired, though
62
+ * there is still a chance that it will be processed
63
+ */
64
+ QUOTE_SOFT_EXPIRED = -1,
65
+ /**
66
+ * Swap quote was created, use the {@link FromBTCSwap.commit} or {@link FromBTCSwap.txsCommit} functions
67
+ * to initiate it by creating the swap escrow on the destination smart chain
68
+ */
69
+ PR_CREATED = 0,
70
+ /**
71
+ * Swap escrow was initiated (committed) on the destination chain, user can send the BTC to the
72
+ * swap address with the {@link FromBTCSwap.getFundedPsbt}, {@link FromBTCSwap.getAddress} or
73
+ * {@link FromBTCSwap.getHyperlink} functions.
74
+ */
75
+ CLAIM_COMMITED = 1,
76
+ /**
77
+ * Input bitcoin transaction was confirmed, wait for automatic settlement by the watchtowers
78
+ * using the {@link FromBTCSwap.waitTillClaimed} function or settle manually using the {@link FromBTCSwap.claim}
79
+ * or {@link FromBTCSwap.txsClaim} function.
80
+ */
81
+ BTC_TX_CONFIRMED = 2,
82
+ /**
83
+ * Swap successfully settled and funds received on the destination chain
84
+ */
85
+ CLAIM_CLAIMED = 3
86
+ }
87
+
88
+ const FromBTCSwapStateDescription = {
89
+ [FromBTCSwapState.FAILED]: "Bitcoin swap address has expired and the intermediary (LP) has already refunded its funds. No BTC should be sent anymore!",
90
+ [FromBTCSwapState.EXPIRED]: "Bitcoin swap address has expired, user should not send any BTC anymore! Though the intermediary (LP) hasn't refunded yet. So if there is a transaction already in-flight the swap might still succeed.",
91
+ [FromBTCSwapState.QUOTE_EXPIRED]: "Swap has expired for good and there is no way how it can be executed anymore",
92
+ [FromBTCSwapState.QUOTE_SOFT_EXPIRED]: "The swap is expired, though there is still a chance that it will be processed",
93
+ [FromBTCSwapState.PR_CREATED]: "Swap quote was created, initiate it by creating the swap escrow on the destination smart chain",
94
+ [FromBTCSwapState.CLAIM_COMMITED]: "Swap escrow was initiated (committed) on the destination chain, user can send the BTC to the Bitcoin swap address.",
95
+ [FromBTCSwapState.BTC_TX_CONFIRMED]: "Input bitcoin transaction was confirmed, wait for automatic settlement by the watchtower or settle manually.",
96
+ [FromBTCSwapState.CLAIM_CLAIMED]: "Swap successfully settled and funds received on the destination chain"
97
+ };
98
+
99
+ export type FromBTCSwapInit<T extends SwapData> = IEscrowSelfInitSwapInit<T> & {
100
+ data: T;
101
+ address?: string;
102
+ amount?: bigint;
103
+ requiredConfirmations?: number;
104
+ };
105
+
106
+ export function isFromBTCSwapInit<T extends SwapData>(obj: any): obj is FromBTCSwapInit<T> {
107
+ return typeof(obj.data) === "object" &&
108
+ (obj.address==null || typeof(obj.address) === "string") &&
109
+ (obj.amount==null || typeof(obj.amount) === "bigint") &&
110
+ (obj.requiredConfirmations==null || typeof(obj.requiredConfirmations) === "number") &&
111
+ isIEscrowSelfInitSwapInit<T>(obj);
112
+ }
113
+
114
+ /**
115
+ * Legacy escrow (PrTLC) based swap for Bitcoin -> Smart chains, requires manual initiation
116
+ * of the swap escrow on the destination chain.
117
+ *
118
+ * @category Swaps/Legacy/Bitcoin → Smart chain
119
+ */
120
+ export class FromBTCSwap<T extends ChainType = ChainType>
121
+ extends IFromBTCSelfInitSwap<T, FromBTCDefinition<T>, FromBTCSwapState>
122
+ implements IBTCWalletSwap, IClaimableSwap<T, FromBTCDefinition<T>, FromBTCSwapState>, IAddressSwap {
123
+
124
+ protected readonly TYPE: SwapType.FROM_BTC = SwapType.FROM_BTC;
125
+ /**
126
+ * @internal
127
+ */
128
+ protected readonly swapStateName = (state: number) => FromBTCSwapState[state];
129
+ /**
130
+ * @internal
131
+ */
132
+ protected readonly swapStateDescription = FromBTCSwapStateDescription;
133
+ /**
134
+ * @internal
135
+ */
136
+ protected readonly logger: LoggerType;
137
+ /**
138
+ * @internal
139
+ */
140
+ protected readonly inputToken: BtcToken<false> = BitcoinTokens.BTC;
141
+ /**
142
+ * @internal
143
+ */
144
+ protected readonly feeRate!: string;
145
+
146
+ /**
147
+ * @internal
148
+ */
149
+ readonly _data!: T["Data"];
150
+
151
+ private address?: string;
152
+ private amount?: bigint;
153
+ private requiredConfirmations?: number;
154
+
155
+ private senderAddress?: string;
156
+ private txId?: string;
157
+ private vout?: number;
158
+
159
+ private btcTxConfirmedAt?: number;
160
+
161
+ constructor(wrapper: FromBTCWrapper<T>, init: FromBTCSwapInit<T["Data"]>);
162
+ constructor(wrapper: FromBTCWrapper<T>, obj: any);
163
+ constructor(wrapper: FromBTCWrapper<T>, initOrObject: FromBTCSwapInit<T["Data"]> | any) {
164
+ if(isFromBTCSwapInit(initOrObject) && initOrObject.url!=null) initOrObject.url += "/frombtc";
165
+ super(wrapper, initOrObject);
166
+ if(isFromBTCSwapInit(initOrObject)) {
167
+ this._state = FromBTCSwapState.PR_CREATED;
168
+ this._data = initOrObject.data;
169
+ this.feeRate = initOrObject.feeRate;
170
+ this.address = initOrObject.address;
171
+ this.amount = initOrObject.amount;
172
+ this.requiredConfirmations = initOrObject.requiredConfirmations;
173
+ } else {
174
+ this.address = initOrObject.address;
175
+ this.amount = toBigInt(initOrObject.amount);
176
+ this.senderAddress = initOrObject.senderAddress;
177
+ this.txId = initOrObject.txId;
178
+ this.vout = initOrObject.vout;
179
+ this.requiredConfirmations = initOrObject.requiredConfirmations ?? this._data.getConfirmationsHint();
180
+ this.btcTxConfirmedAt = initOrObject.btcTxConfirmedAt;
181
+ }
182
+ this.tryRecomputeSwapPrice();
183
+ this.logger = getLogger("FromBTC("+this.getIdentifierHashString()+"): ");
184
+ }
185
+
186
+ /**
187
+ * @inheritDoc
188
+ * @internal
189
+ */
190
+ protected getSwapData(): T["Data"] {
191
+ return this._data;
192
+ }
193
+
194
+ /**
195
+ * @inheritDoc
196
+ * @internal
197
+ */
198
+ protected upgradeVersion() {
199
+ if(this.version == null) {
200
+ switch(this._state) {
201
+ case -2:
202
+ this._state = FromBTCSwapState.FAILED
203
+ break;
204
+ case -1:
205
+ this._state = FromBTCSwapState.QUOTE_EXPIRED
206
+ break;
207
+ case 0:
208
+ this._state = FromBTCSwapState.PR_CREATED
209
+ break;
210
+ case 1:
211
+ this._state = FromBTCSwapState.CLAIM_COMMITED
212
+ break;
213
+ case 2:
214
+ this._state = FromBTCSwapState.BTC_TX_CONFIRMED
215
+ break;
216
+ case 3:
217
+ this._state = FromBTCSwapState.CLAIM_CLAIMED
218
+ break;
219
+ }
220
+ this.version = 1;
221
+ }
222
+ }
223
+
224
+
225
+ //////////////////////////////
226
+ //// Getters & utils
227
+
228
+ /**
229
+ * Returns bitcoin address where the on-chain BTC should be sent to
230
+ */
231
+ getAddress(): string {
232
+ if(this._state===FromBTCSwapState.PR_CREATED) throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
233
+ return this.address ?? "";
234
+ }
235
+
236
+ /**
237
+ * Unsafe bitcoin hyperlink getter, returns the address even before the swap is committed!
238
+ *
239
+ * @private
240
+ */
241
+ private _getHyperlink(): string {
242
+ return this.address==null || this.amount==null ? "" : "bitcoin:"+this.address+"?amount="+encodeURIComponent((Number(this.amount) / 100000000).toString(10));
243
+ }
244
+
245
+ /**
246
+ * @inheritDoc
247
+ */
248
+ getHyperlink(): string {
249
+ if(this._state===FromBTCSwapState.PR_CREATED) throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
250
+ return this._getHyperlink();
251
+ }
252
+
253
+ /**
254
+ * @inheritDoc
255
+ */
256
+ getInputAddress(): string | null {
257
+ return this.senderAddress ?? null;
258
+ }
259
+
260
+ /**
261
+ * @inheritDoc
262
+ */
263
+ getInputTxId(): string | null {
264
+ return this.txId ?? null;
265
+ }
266
+
267
+ /**
268
+ * Returns timeout time (in UNIX milliseconds) when the on-chain address will expire and no funds should be sent
269
+ * to that address anymore
270
+ */
271
+ getTimeoutTime(): number {
272
+ return Number(this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations ?? 6)) * 1000;
273
+ }
274
+
275
+ /**
276
+ * @inheritDoc
277
+ */
278
+ requiresAction(): boolean {
279
+ return this.isClaimable() || (this._state===FromBTCSwapState.CLAIM_COMMITED && this.getTimeoutTime()>Date.now() && this.txId==null);
280
+ }
281
+
282
+ /**
283
+ * @inheritDoc
284
+ */
285
+ isFinished(): boolean {
286
+ return this._state===FromBTCSwapState.CLAIM_CLAIMED || this._state===FromBTCSwapState.QUOTE_EXPIRED || this._state===FromBTCSwapState.FAILED;
287
+ }
288
+
289
+ /**
290
+ * @inheritDoc
291
+ */
292
+ isClaimable(): boolean {
293
+ return this._state===FromBTCSwapState.BTC_TX_CONFIRMED;
294
+ }
295
+
296
+ /**
297
+ * @inheritDoc
298
+ */
299
+ isSuccessful(): boolean {
300
+ return this._state===FromBTCSwapState.CLAIM_CLAIMED;
301
+ }
302
+
303
+ /**
304
+ * @inheritDoc
305
+ */
306
+ isFailed(): boolean {
307
+ return this._state===FromBTCSwapState.FAILED || this._state===FromBTCSwapState.EXPIRED;
308
+ }
309
+
310
+ /**
311
+ * @inheritDoc
312
+ */
313
+ isInProgress(): boolean {
314
+ return this._state===FromBTCSwapState.CLAIM_COMMITED ||
315
+ this._state===FromBTCSwapState.BTC_TX_CONFIRMED;
316
+ }
317
+
318
+ /**
319
+ * @inheritDoc
320
+ */
321
+ isQuoteExpired(): boolean {
322
+ return this._state===FromBTCSwapState.QUOTE_EXPIRED;
323
+ }
324
+
325
+ /**
326
+ * @inheritDoc
327
+ */
328
+ isQuoteSoftExpired(): boolean {
329
+ return this._state===FromBTCSwapState.QUOTE_EXPIRED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED;
330
+ }
331
+
332
+ /**
333
+ * @inheritDoc
334
+ * @internal
335
+ */
336
+ protected canCommit(skipQuoteExpiryChecks?: boolean): boolean {
337
+ if(this._state!==FromBTCSwapState.PR_CREATED && (!skipQuoteExpiryChecks || this._state!==FromBTCSwapState.QUOTE_SOFT_EXPIRED)) return false;
338
+ if(this.requiredConfirmations==null) return false;
339
+ const expiry = this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations);
340
+ const currentTimestamp = BigInt(Math.floor(Date.now()/1000));
341
+
342
+ return (expiry - currentTimestamp) >= this.wrapper._options.minSendWindow;
343
+ }
344
+
345
+
346
+ //////////////////////////////
347
+ //// Amounts & fees
348
+
349
+ /**
350
+ * @inheritDoc
351
+ */
352
+ getInputToken(): BtcToken<false> {
353
+ return BitcoinTokens.BTC;
354
+ }
355
+
356
+ /**
357
+ * @inheritDoc
358
+ */
359
+ getInput(): TokenAmount<BtcToken<false>> {
360
+ return toTokenAmount(this.amount ?? null, this.inputToken, this.wrapper._prices);
361
+ }
362
+
363
+ /**
364
+ * Returns claimer bounty, acting as a reward for watchtowers to claim the swap automatically,
365
+ * this amount is pre-funded by the user on the destination chain when the swap escrow
366
+ * is initiated. For total pre-funded deposit amount see {@link getTotalDeposit}.
367
+ */
368
+ getClaimerBounty(): TokenAmount<SCToken<T["ChainId"]>, true> {
369
+ return toTokenAmount(this._data.getClaimerBounty(), this.wrapper._tokens[this._data.getDepositToken()], this.wrapper._prices);
370
+ }
371
+
372
+
373
+ //////////////////////////////
374
+ //// Bitcoin tx
375
+
376
+ /**
377
+ * If the required number of confirmations is not known, this function tries to infer it by looping through
378
+ * possible confirmation targets and comparing the claim hashes
379
+ *
380
+ * @param btcTx Bitcoin transaction
381
+ * @param vout Output index of the desired output in the bitcoin transaction
382
+ *
383
+ * @private
384
+ */
385
+ private inferRequiredConfirmationsCount(btcTx: Omit<BtcTxWithBlockheight, "hex" | "raw">, vout: number): number | undefined {
386
+ const txOut = btcTx.outs[vout];
387
+ for(let i=1;i<=20;i++) {
388
+ const computedClaimHash = this._contract.getHashForOnchain(
389
+ Buffer.from(txOut.scriptPubKey.hex, "hex"),
390
+ BigInt(txOut.value),
391
+ i
392
+ );
393
+ if(computedClaimHash.toString("hex")===this._data.getClaimHash()) {
394
+ return i;
395
+ }
396
+ }
397
+ }
398
+
399
+ /**
400
+ * @inheritDoc
401
+ */
402
+ getRequiredConfirmationsCount(): number {
403
+ return this.requiredConfirmations ?? NaN;
404
+ }
405
+
406
+ /**
407
+ * Checks whether a bitcoin payment was already made, returns the payment or `null` when no payment has been made.
408
+ *
409
+ * @internal
410
+ */
411
+ protected async getBitcoinPayment(): Promise<{
412
+ txId: string,
413
+ vout: number,
414
+ confirmations: number,
415
+ targetConfirmations: number,
416
+ inputAddresses?: string[]
417
+ } | null> {
418
+ const txoHashHint = this._data.getTxoHashHint();
419
+ if(txoHashHint==null) throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
420
+ if(this.address==null) throw new Error("Cannot check bitcoin payment, because the address is not known! This can happen after a swap is recovered.");
421
+
422
+ const result = await this.wrapper._btcRpc.checkAddressTxos(this.address, Buffer.from(txoHashHint, "hex"));
423
+ if(result==null) return null;
424
+
425
+ if(this.requiredConfirmations==null) {
426
+ this.requiredConfirmations = this.inferRequiredConfirmationsCount(result.tx, result.vout);
427
+ }
428
+
429
+ return {
430
+ inputAddresses: result.tx.inputAddresses,
431
+ txId: result.tx.txid,
432
+ vout: result.vout,
433
+ confirmations: result.tx.confirmations ?? 0,
434
+ targetConfirmations: this.getRequiredConfirmationsCount()
435
+ }
436
+ }
437
+
438
+ /**
439
+ * Used to set the txId of the bitcoin payment from the on-chain events listener
440
+ *
441
+ * @param txId Transaction ID that settled the swap on the smart chain
442
+ *
443
+ * @internal
444
+ */
445
+ async _setBitcoinTxId(txId: string) {
446
+ if(this.txId!==txId || this.address==null || this.vout==null || this.senderAddress==null || this.amount==null) {
447
+ const btcTx = await this.wrapper._btcRpc.getTransaction(txId);
448
+ if(btcTx==null) return;
449
+
450
+ const txoHashHint = this._data.getTxoHashHint();
451
+ if(txoHashHint!=null) {
452
+ const expectedTxoHash = Buffer.from(txoHashHint, "hex");
453
+ const vout = btcTx.outs.findIndex(out => getTxoHash(out.scriptPubKey.hex, out.value).equals(expectedTxoHash));
454
+ if(vout!==-1) {
455
+ this.vout = vout;
456
+ //If amount or address are not known, parse them from the bitcoin tx
457
+ // this can happen if the swap is recovered from on-chain data and
458
+ // hence doesn't contain the address and amount data
459
+ if(this.amount==null) this.amount = BigInt(btcTx.outs[vout].value);
460
+ if(this.address==null) try {
461
+ this.address = fromOutputScript(this.wrapper._options.bitcoinNetwork, btcTx.outs[vout].scriptPubKey.hex);
462
+ } catch (e: any) {
463
+ this.logger.warn("_setBitcoinTxId(): Failed to parse address from output script: ", e);
464
+ }
465
+ if(this.requiredConfirmations==null) {
466
+ this.requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
467
+ }
468
+ }
469
+ }
470
+
471
+ if(btcTx.inputAddresses!=null) {
472
+ this.senderAddress = btcTx.inputAddresses[0];
473
+ }
474
+ }
475
+
476
+ this.txId = txId;
477
+ }
478
+
479
+ /**
480
+ * @inheritDoc
481
+ *
482
+ * @throws {Error} if in invalid state (must be {@link FromBTCSwapState.CLAIM_COMMITED})
483
+ */
484
+ async waitForBitcoinTransaction(
485
+ updateCallback?: (txId?: string, confirmations?: number, targetConfirmations?: number, txEtaMs?: number) => void,
486
+ checkIntervalSeconds?: number,
487
+ abortSignal?: AbortSignal
488
+ ): Promise<string> {
489
+ if(this._state!==FromBTCSwapState.CLAIM_COMMITED && this._state!==FromBTCSwapState.EXPIRED) throw new Error("Must be in COMMITED state!");
490
+ const txoHashHint = this._data.getTxoHashHint();
491
+ if(txoHashHint==null) throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
492
+ if(this.address==null) throw new Error("Cannot check bitcoin payment, because the address is not known! This can happen after a swap is recovered.");
493
+
494
+ let abortedDueToEnoughConfirmationsResult: {
495
+ tx: Omit<BtcTxWithBlockheight, "hex" | "raw">, vout: number
496
+ } | undefined;
497
+ const abortController = extendAbortController(abortSignal);
498
+
499
+ const result = await this.wrapper._btcRpc.waitForAddressTxo(
500
+ this.address,
501
+ Buffer.from(txoHashHint, "hex"),
502
+ this.requiredConfirmations ?? 6, //In case confirmation count is not known, we use a conservative estimate
503
+ (btcTx?: Omit<BtcTxWithBlockheight, "hex" | "raw">, vout?: number, txEtaMs?: number) => {
504
+ let requiredConfirmations = this.requiredConfirmations;
505
+
506
+ if(btcTx!=null && vout!=null && requiredConfirmations==null) {
507
+ requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
508
+ }
509
+
510
+ if(btcTx!=null && (btcTx.txid!==this.txId || (this.requiredConfirmations==null && requiredConfirmations!=null))) {
511
+ this.txId = btcTx.txid;
512
+ this.vout = vout;
513
+ this.requiredConfirmations = requiredConfirmations;
514
+ if(btcTx.inputAddresses!=null) this.senderAddress = btcTx.inputAddresses[0];
515
+ this._saveAndEmit().catch(e => {
516
+ this.logger.error("waitForBitcoinTransaction(): Failed to save swap from within waitForAddressTxo callback:", e)
517
+ });
518
+ }
519
+
520
+ //Abort the loop as soon as the transaction gets enough confirmations, this is required in case
521
+ // we pass a default 6 confirmations to the fn, but then are able to infer the actual confirmation
522
+ // target from the prior block
523
+ if(btcTx?.confirmations!=null && requiredConfirmations!=null && requiredConfirmations<=btcTx.confirmations && vout!=null) {
524
+ abortedDueToEnoughConfirmationsResult = {
525
+ tx: btcTx,
526
+ vout
527
+ };
528
+ abortController.abort();
529
+ return;
530
+ }
531
+
532
+ if(updateCallback!=null) updateCallback(btcTx?.txid, btcTx==null ? undefined : (btcTx?.confirmations ?? 0), requiredConfirmations ?? NaN, txEtaMs);
533
+ },
534
+ abortController.signal,
535
+ checkIntervalSeconds
536
+ ).catch(e => {
537
+ //We catch the case when the loop was aborted due to the transaction getting enough confirmations
538
+ if(abortedDueToEnoughConfirmationsResult!=null) return abortedDueToEnoughConfirmationsResult;
539
+ throw e;
540
+ });
541
+
542
+ if(abortSignal!=null) abortSignal.throwIfAborted();
543
+
544
+ this.txId = result.tx.txid;
545
+ this.vout = result.vout;
546
+ if(result.tx.inputAddresses!=null) this.senderAddress = result.tx.inputAddresses[0];
547
+
548
+ if(
549
+ (this._state as FromBTCSwapState)!==FromBTCSwapState.CLAIM_CLAIMED &&
550
+ (this._state as FromBTCSwapState)!==FromBTCSwapState.FAILED
551
+ ) {
552
+ this.btcTxConfirmedAt ??= Date.now();
553
+ this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
554
+ }
555
+
556
+ await this._saveAndEmit();
557
+
558
+ return result.tx.txid;
559
+ }
560
+
561
+ /**
562
+ * Private getter of the funded PSBT that doesn't check current state
563
+ *
564
+ * @param _bitcoinWallet Bitcoin wallet to fund the PSBT with
565
+ * @param feeRate Optional bitcoin fee rate in sats/vB
566
+ * @param additionalOutputs Optional additional outputs that should also be included in the generated PSBT
567
+ *
568
+ * @private
569
+ */
570
+ private async _getFundedPsbt(
571
+ _bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface,
572
+ feeRate?: number,
573
+ additionalOutputs?: ({amount: bigint, outputScript: Uint8Array} | {amount: bigint, address: string})[]
574
+ ): Promise<{psbt: Transaction, psbtHex: string, psbtBase64: string, signInputs: number[]}> {
575
+ if(this.address==null) throw new Error("Cannot create funded PSBT, because the address is not known! This can happen after a swap is recovered.");
576
+
577
+ let bitcoinWallet: IBitcoinWallet;
578
+ if(isIBitcoinWallet(_bitcoinWallet)) {
579
+ bitcoinWallet = _bitcoinWallet;
580
+ } else {
581
+ bitcoinWallet = new SingleAddressBitcoinWallet(this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork, _bitcoinWallet);
582
+ }
583
+ //TODO: Maybe re-introduce fee rate check here if passed from the user
584
+ if(feeRate==null) {
585
+ feeRate = await bitcoinWallet.getFeeRate();
586
+ }
587
+
588
+ const basePsbt = new Transaction({
589
+ allowUnknownOutputs: true,
590
+ allowLegacyWitnessUtxo: true
591
+ });
592
+ basePsbt.addOutput({
593
+ amount: this.amount,
594
+ script: toOutputScript(this.wrapper._options.bitcoinNetwork, this.address)
595
+ });
596
+ if(additionalOutputs!=null) additionalOutputs.forEach(output => {
597
+ basePsbt.addOutput({
598
+ amount: output.amount,
599
+ script: (output as {outputScript: Uint8Array}).outputScript ?? toOutputScript(this.wrapper._options.bitcoinNetwork, (output as {address: string}).address)
600
+ });
601
+ });
602
+
603
+ const psbt = await bitcoinWallet.fundPsbt(basePsbt, feeRate);
604
+ //Sign every input
605
+ const signInputs: number[] = [];
606
+ for(let i=0;i<psbt.inputsLength;i++) {
607
+ signInputs.push(i);
608
+ }
609
+ const serializedPsbt = Buffer.from(psbt.toPSBT());
610
+ return {
611
+ psbt,
612
+ psbtHex: serializedPsbt.toString("hex"),
613
+ psbtBase64: serializedPsbt.toString("base64"),
614
+ signInputs
615
+ };
616
+ }
617
+
618
+ /**
619
+ * @inheritDoc
620
+ */
621
+ getFundedPsbt(
622
+ _bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface,
623
+ feeRate?: number,
624
+ additionalOutputs?: ({amount: bigint, outputScript: Uint8Array} | {amount: bigint, address: string})[]
625
+ ) {
626
+ if(this._state!==FromBTCSwapState.CLAIM_COMMITED)
627
+ throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
628
+ return this._getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs);
629
+ }
630
+
631
+ /**
632
+ * @inheritDoc
633
+ *
634
+ * @throws {Error} if the swap is in invalid state (not in {@link FromBTCSwapState.CLAIM_COMMITED}), or if
635
+ * the swap bitcoin address already expired.
636
+ */
637
+ async submitPsbt(_psbt: Transaction | string): Promise<string> {
638
+ const psbt = parsePsbtTransaction(_psbt);
639
+ if(this._state!==FromBTCSwapState.CLAIM_COMMITED)
640
+ throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
641
+
642
+ //Ensure not expired
643
+ if(this.getTimeoutTime()<Date.now()) {
644
+ throw new Error("Swap address expired!");
645
+ }
646
+
647
+ const output0 = psbt.getOutput(0);
648
+ if(this.amount!=null && output0.amount!==this.amount)
649
+ throw new Error("PSBT output amount invalid, expected: "+this.amount+" got: "+output0.amount);
650
+ if(this.address!=null) {
651
+ const expectedOutputScript = toOutputScript(this.wrapper._options.bitcoinNetwork, this.address);
652
+ if(output0.script==null || !expectedOutputScript.equals(output0.script))
653
+ throw new Error("PSBT output script invalid!");
654
+ }
655
+
656
+ if(!psbt.isFinal) psbt.finalize();
657
+
658
+ return await this.wrapper._btcRpc.sendRawTransaction(Buffer.from(psbt.toBytes(true, true)).toString("hex"));
659
+ }
660
+
661
+ /**
662
+ * @inheritDoc
663
+ */
664
+ async estimateBitcoinFee(_bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface, feeRate?: number): Promise<TokenAmount<BtcToken<false>, true> | null> {
665
+ if(this.address==null || this.amount==null) return null;
666
+ const bitcoinWallet: IBitcoinWallet = toBitcoinWallet(_bitcoinWallet, this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork);
667
+ const txFee = await bitcoinWallet.getTransactionFee(this.address, this.amount, feeRate);
668
+ if(txFee==null) return null;
669
+ return toTokenAmount(BigInt(txFee), BitcoinTokens.BTC, this.wrapper._prices);
670
+ }
671
+
672
+ /**
673
+ * @inheritDoc
674
+ */
675
+ async sendBitcoinTransaction(wallet: IBitcoinWallet | MinimalBitcoinWalletInterfaceWithSigner, feeRate?: number): Promise<string> {
676
+ if(this.address==null || this.amount==null) throw new Error("Cannot send bitcoin transaction, because the address is not known! This can happen after a swap is recovered.");
677
+
678
+ if(this._state!==FromBTCSwapState.CLAIM_COMMITED)
679
+ throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
680
+
681
+ //Ensure not expired
682
+ if(this.getTimeoutTime()<Date.now()) {
683
+ throw new Error("Swap address expired!");
684
+ }
685
+
686
+ if(isIBitcoinWallet(wallet)) {
687
+ return await wallet.sendTransaction(this.address, this.amount, feeRate);
688
+ } else {
689
+ const {psbt, psbtHex, psbtBase64, signInputs} = await this.getFundedPsbt(wallet, feeRate);
690
+ const signedPsbt = await wallet.signPsbt({
691
+ psbt, psbtHex, psbtBase64
692
+ }, signInputs);
693
+ return await this.submitPsbt(signedPsbt);
694
+ }
695
+ }
696
+
697
+
698
+ //////////////////////////////
699
+ //// Execution
700
+
701
+ /**
702
+ * Executes the swap with the provided bitcoin wallet,
703
+ *
704
+ * @param dstSigner Signer on the destination network, needs to have the same address as the one specified when
705
+ * quote was created, this is required for legacy swaps because the destination wallet needs to actively open
706
+ * a bitcoin swap address to which the BTC is then sent, this means that the address also needs to have enough
707
+ * native tokens to pay for gas on the destination network
708
+ * @param wallet Bitcoin wallet to use to sign the bitcoin transaction, can also be null - then the execution waits
709
+ * till a transaction is received from an external wallet
710
+ * @param callbacks Callbacks to track the progress of the swap
711
+ * @param options Optional options for the swap like feeRate, AbortSignal, and timeouts/intervals
712
+ *
713
+ * @returns {boolean} Whether a swap was settled automatically by swap watchtowers or requires manual claim by the
714
+ * user, in case `false` is returned the user should call `swap.claim()` to settle the swap on the destination manually
715
+ */
716
+ async execute(
717
+ dstSigner: T["Signer"] | T["NativeSigner"],
718
+ wallet?: IBitcoinWallet | MinimalBitcoinWalletInterfaceWithSigner | null | undefined,
719
+ callbacks?: {
720
+ onDestinationCommitSent?: (destinationCommitTxId: string) => void,
721
+ onSourceTransactionSent?: (sourceTxId: string) => void,
722
+ onSourceTransactionConfirmationStatus?: (sourceTxId?: string, confirmations?: number, targetConfirations?: number, etaMs?: number) => void,
723
+ onSourceTransactionConfirmed?: (sourceTxId: string) => void,
724
+ onSwapSettled?: (destinationTxId: string) => void
725
+ },
726
+ options?: {
727
+ feeRate?: number,
728
+ abortSignal?: AbortSignal,
729
+ btcTxCheckIntervalSeconds?: number,
730
+ maxWaitTillAutomaticSettlementSeconds?: number
731
+ }
732
+ ): Promise<boolean> {
733
+ if(this._state===FromBTCSwapState.FAILED) throw new Error("Swap failed!");
734
+ if(this._state===FromBTCSwapState.EXPIRED) throw new Error("Swap address expired!");
735
+ if(this._state===FromBTCSwapState.QUOTE_EXPIRED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) throw new Error("Swap quote expired!");
736
+ if(this._state===FromBTCSwapState.CLAIM_CLAIMED) throw new Error("Swap already settled!");
737
+
738
+ if(this._state===FromBTCSwapState.PR_CREATED) {
739
+ await this.commit(dstSigner, options?.abortSignal, undefined, callbacks?.onDestinationCommitSent);
740
+ }
741
+ if(this._state===FromBTCSwapState.CLAIM_COMMITED) {
742
+ if(wallet!=null) {
743
+ const bitcoinPaymentSent = await this.getBitcoinPayment();
744
+
745
+ if(bitcoinPaymentSent==null) {
746
+ //Send btc tx
747
+ const txId = await this.sendBitcoinTransaction(wallet, options?.feeRate);
748
+ if(callbacks?.onSourceTransactionSent!=null) callbacks.onSourceTransactionSent(txId);
749
+ }
750
+ }
751
+
752
+ const txId = await this.waitForBitcoinTransaction(callbacks?.onSourceTransactionConfirmationStatus, options?.btcTxCheckIntervalSeconds, options?.abortSignal);
753
+ if (callbacks?.onSourceTransactionConfirmed != null) callbacks.onSourceTransactionConfirmed(txId);
754
+ }
755
+
756
+ // @ts-ignore
757
+ if(this._state===FromBTCSwapState.CLAIM_CLAIMED) return true;
758
+
759
+ if(this._state===FromBTCSwapState.BTC_TX_CONFIRMED) {
760
+ const success = await this.waitTillClaimed(options?.maxWaitTillAutomaticSettlementSeconds ?? 60, options?.abortSignal);
761
+ if(success && callbacks?.onSwapSettled!=null) callbacks.onSwapSettled(this.getOutputTxId()!);
762
+ return success;
763
+ }
764
+
765
+ throw new Error("Invalid state reached!");
766
+ }
767
+
768
+ /**
769
+ * @inheritDoc
770
+ *
771
+ * @param options.bitcoinFeeRate Optional fee rate to use for the created Bitcoin transaction
772
+ * @param options.bitcoinWallet Bitcoin wallet to use, when provided the function returns a funded
773
+ * psbt (`"FUNDED_PSBT"`), if not passed just a bitcoin receive address is returned (`"ADDRESS"`)
774
+ * @param options.skipChecks Skip checks like making sure init signature is still valid and swap
775
+ * wasn't commited yet (this is handled on swap creation, if you commit right after quoting, you
776
+ * can use `skipChecks=true`)
777
+ *
778
+ * @throws {Error} if the swap or quote is expired, or if triggered in invalid state
779
+ */
780
+ async txsExecute(options?: {
781
+ bitcoinFeeRate?: number,
782
+ bitcoinWallet?: MinimalBitcoinWalletInterface,
783
+ skipChecks?: boolean
784
+ }) {
785
+ if(this._state===FromBTCSwapState.PR_CREATED) {
786
+ if(!await this._verifyQuoteValid()) throw new Error("Quote already expired or close to expiry!");
787
+ if(this.getTimeoutTime()<Date.now()) throw new Error("Swap address already expired or close to expiry!");
788
+ return [
789
+ {
790
+ name: "Commit" as const,
791
+ description: `Opens up the bitcoin swap address on the ${this.chainIdentifier} side`,
792
+ chain: this.chainIdentifier,
793
+ txs: await this.txsCommit(options?.skipChecks)
794
+ },
795
+ {
796
+ name: "Payment" as const,
797
+ description: "Send funds to the bitcoin swap address",
798
+ chain: "BITCOIN" as const,
799
+ txs: [
800
+ options?.bitcoinWallet==null ? {
801
+ address: this.address ?? "",
802
+ amount: Number(this.amount),
803
+ hyperlink: this._getHyperlink(),
804
+ type: "ADDRESS" as const
805
+ } : {
806
+ ...await this.getFundedPsbt(options.bitcoinWallet),
807
+ type: "FUNDED_PSBT" as const
808
+ }
809
+ ]
810
+ }
811
+ ];
812
+ }
813
+
814
+ if(this._state===FromBTCSwapState.CLAIM_COMMITED) {
815
+ if(this.getTimeoutTime()<Date.now()) throw new Error("Swap address already expired or close to expiry!");
816
+ return [
817
+ {
818
+ name: "Payment" as const,
819
+ description: "Send funds to the bitcoin swap address",
820
+ chain: "BITCOIN" as const,
821
+ txs: [
822
+ options?.bitcoinWallet==null ? {
823
+ address: this.getAddress(),
824
+ amount: Number(this.amount),
825
+ hyperlink: this._getHyperlink(),
826
+ type: "ADDRESS" as const
827
+ } : {
828
+ ...await this.getFundedPsbt(options.bitcoinWallet),
829
+ type: "FUNDED_PSBT" as const
830
+ }
831
+ ]
832
+ }
833
+ ];
834
+ }
835
+
836
+ throw new Error("Invalid swap state to obtain execution txns, required PR_CREATED or CLAIM_COMMITED");
837
+ }
838
+
839
+ /**
840
+ * @inheritDoc
841
+ *
842
+ * @param options.bitcoinFeeRate Optional fee rate to use for the created Bitcoin transaction
843
+ * @param options.bitcoinWallet Bitcoin wallet to use, when provided the function returns a funded
844
+ * psbt (`"FUNDED_PSBT"`), if not passed just a bitcoin receive address is returned (`"ADDRESS"`)
845
+ * @param options.skipChecks Skip checks like making sure init signature is still valid and swap
846
+ * wasn't commited yet (this is handled on swap creation, if you commit right after quoting, you
847
+ * can use `skipChecks=true`)
848
+ * @param options.manualSettlementSmartChainSigner Optional smart chain signer to create a manual claim (settlement) transaction
849
+ * @param options.maxWaitTillAutomaticSettlementSeconds Maximum time to wait for an automatic settlement after
850
+ * the bitcoin transaction is confirmed (defaults to 60 seconds)
851
+ */
852
+ async getCurrentActions(options?: {
853
+ bitcoinFeeRate?: number,
854
+ bitcoinWallet?: MinimalBitcoinWalletInterface,
855
+ skipChecks?: boolean,
856
+ manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
857
+ maxWaitTillAutomaticSettlementSeconds?: number
858
+ }): Promise<SwapExecutionAction<T>[]> {
859
+ if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.CLAIM_COMMITED) {
860
+ try {
861
+ return await this.txsExecute(options);
862
+ } catch (e) {}
863
+ }
864
+ if(this.isClaimable()) {
865
+ if(
866
+ this.btcTxConfirmedAt==null ||
867
+ options?.maxWaitTillAutomaticSettlementSeconds===0 ||
868
+ (Date.now() - this.btcTxConfirmedAt) > (options?.maxWaitTillAutomaticSettlementSeconds ?? 60)*1000
869
+ ) {
870
+ return [{
871
+ name: "Claim" as const,
872
+ description: "Manually settle (claim) the swap on the destination smart chain",
873
+ chain: this.chainIdentifier,
874
+ txs: await this.txsClaim(options?.manualSettlementSmartChainSigner)
875
+ }];
876
+ }
877
+ }
878
+ return[];
879
+ }
880
+
881
+ //////////////////////////////
882
+ //// Commit
883
+
884
+ /**
885
+ * @inheritDoc
886
+ *
887
+ * @throws {Error} If invalid signer is provided that doesn't match the swap data
888
+ */
889
+ async commit(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, skipChecks?: boolean, onBeforeTxSent?: (txId: string) => void): Promise<string> {
890
+ const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
891
+ this.checkSigner(signer);
892
+ let txCount = 0;
893
+ const txs = await this.txsCommit(skipChecks);
894
+ const result = await this.wrapper._chain.sendAndConfirm(
895
+ signer, txs, true, abortSignal, undefined, (txId: string) => {
896
+ txCount++;
897
+ if(onBeforeTxSent!=null && txCount===txs.length) onBeforeTxSent(txId);
898
+ return Promise.resolve();
899
+ }
900
+ );
901
+
902
+ this._commitTxId = result[result.length - 1];
903
+ if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED || this._state===FromBTCSwapState.QUOTE_EXPIRED) {
904
+ await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
905
+ }
906
+ return this._commitTxId;
907
+ }
908
+
909
+ /**
910
+ * @inheritDoc
911
+ */
912
+ async waitTillCommited(abortSignal?: AbortSignal): Promise<void> {
913
+ if(this._state===FromBTCSwapState.CLAIM_COMMITED || this._state===FromBTCSwapState.CLAIM_CLAIMED) return Promise.resolve();
914
+ if(this._state!==FromBTCSwapState.PR_CREATED && this._state!==FromBTCSwapState.QUOTE_SOFT_EXPIRED) throw new Error("Invalid state");
915
+
916
+ const abortController = extendAbortController(abortSignal);
917
+ const result = await Promise.race([
918
+ this.watchdogWaitTillCommited(undefined, abortController.signal),
919
+ this.waitTillState(FromBTCSwapState.CLAIM_COMMITED, "gte", abortController.signal).then(() => 0)
920
+ ]);
921
+ abortController.abort();
922
+
923
+ if(result===0) this.logger.debug("waitTillCommited(): Resolved from state changed");
924
+ if(result===true) this.logger.debug("waitTillCommited(): Resolved from watchdog - commited");
925
+ if(result===false) {
926
+ this.logger.debug("waitTillCommited(): Resolved from watchdog - signature expired");
927
+ if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
928
+ await this._saveAndEmit(FromBTCSwapState.QUOTE_EXPIRED);
929
+ }
930
+ return;
931
+ }
932
+
933
+ if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
934
+ await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
935
+ }
936
+ }
937
+
938
+
939
+ //////////////////////////////
940
+ //// Claim
941
+
942
+ /**
943
+ * Returns transactions for settling (claiming) the swap if the swap requires manual settlement, you can check so
944
+ * with isClaimable. After sending the transaction manually be sure to call the waitTillClaimed function to wait
945
+ * till the claim transaction is observed, processed by the SDK and state of the swap properly updated.
946
+ *
947
+ * @remarks
948
+ * Might also return transactions necessary to sync the bitcoin light client.
949
+ *
950
+ * @param _signer Address of the signer to create the claim transactions for
951
+ *
952
+ * @throws {Error} If the swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
953
+ */
954
+ async txsClaim(_signer?: string | T["Signer"] | T["NativeSigner"]): Promise<T["TX"][]> {
955
+ let signer: string | T["Signer"] | undefined = undefined;
956
+ if(_signer!=null) {
957
+ if (typeof (_signer) === "string") {
958
+ signer = _signer;
959
+ } else if (isAbstractSigner(_signer)) {
960
+ signer = _signer;
961
+ } else {
962
+ signer = await this.wrapper._chain.wrapSigner(_signer);
963
+ }
964
+ }
965
+
966
+ if(this._state!==FromBTCSwapState.BTC_TX_CONFIRMED) throw new Error("Must be in BTC_TX_CONFIRMED state!");
967
+ if(this.txId==null || this.vout==null) throw new Error("Bitcoin transaction ID not known!");
968
+
969
+ const tx = await this.wrapper._btcRpc.getTransaction(this.txId);
970
+ if(tx==null) throw new Error("Bitcoin transaction not found on the network!");
971
+
972
+ this.requiredConfirmations ??= this.inferRequiredConfirmationsCount(tx, this.vout);
973
+ if(this.requiredConfirmations==null)
974
+ throw new Error("Cannot create claim transaction, because required confirmations are not known and cannot be infered! This can happen after a swap is recovered.");
975
+
976
+ if(tx.blockhash==null || tx.confirmations==null || tx.blockheight==null || tx.confirmations<this.requiredConfirmations)
977
+ throw new Error("Bitcoin transaction not confirmed yet!");
978
+
979
+ return await this._contract.txsClaimWithTxData(signer ?? this._getInitiator(), this._data, {
980
+ blockhash: tx.blockhash,
981
+ confirmations: tx.confirmations,
982
+ txid: tx.txid,
983
+ hex: tx.hex,
984
+ height: tx.blockheight
985
+ }, this.requiredConfirmations, this.vout, undefined, this.wrapper._synchronizer(this._contractVersion), true);
986
+ }
987
+
988
+ /**
989
+ * Settles the swap by claiming the funds on the destination chain if the swap requires manual settlement, you can
990
+ * check so with isClaimable.
991
+ *
992
+ * @remarks
993
+ * Might also sync the bitcoin light client during the process.
994
+ *
995
+ * @param _signer Signer to use for signing the settlement transactions, can also be different to the recipient
996
+ * @param abortSignal Abort signal
997
+ * @param onBeforeTxSent Optional callback triggered before the claim transaction is broadcasted
998
+ *
999
+ * @returns Transaction ID of the settlement (claim) transaction on the destination smart chain
1000
+ */
1001
+ async claim(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, onBeforeTxSent?: (txId: string) => void): Promise<string> {
1002
+ const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
1003
+ let txIds: string[];
1004
+ try {
1005
+ let txCount = 0;
1006
+ const txs = await this.txsClaim(signer);
1007
+ txIds = await this.wrapper._chain.sendAndConfirm(
1008
+ signer, txs, true, abortSignal, undefined, (txId: string) => {
1009
+ txCount++;
1010
+ if(onBeforeTxSent!=null && txCount===txs.length) onBeforeTxSent(txId);
1011
+ return Promise.resolve();
1012
+ }
1013
+ );
1014
+ } catch (e) {
1015
+ this.logger.info("claim(): Failed to claim ourselves, checking swap claim state...");
1016
+ if(this._state===FromBTCSwapState.CLAIM_CLAIMED) {
1017
+ this.logger.info("claim(): Transaction state is CLAIM_CLAIMED, swap was successfully claimed by the watchtower");
1018
+ return this._claimTxId!;
1019
+ }
1020
+ const status = await this._contract.getCommitStatus(this._getInitiator(), this._data);
1021
+ if(status?.type===SwapCommitStateType.PAID) {
1022
+ this.logger.info("claim(): Transaction commit status is PAID, swap was successfully claimed by the watchtower");
1023
+ if(this._claimTxId==null) this._claimTxId = await status.getClaimTxId();
1024
+ const txId = Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
1025
+ await this._setBitcoinTxId(txId);
1026
+ await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
1027
+ return this._claimTxId;
1028
+ }
1029
+ throw e;
1030
+ }
1031
+
1032
+ this._claimTxId = txIds[txIds.length - 1];
1033
+ if(
1034
+ this._state===FromBTCSwapState.CLAIM_COMMITED || this._state===FromBTCSwapState.BTC_TX_CONFIRMED ||
1035
+ this._state===FromBTCSwapState.EXPIRED || this._state===FromBTCSwapState.FAILED
1036
+ ) {
1037
+ await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
1038
+ }
1039
+ return txIds[txIds.length - 1];
1040
+ }
1041
+
1042
+ /**
1043
+ * @inheritDoc
1044
+ *
1045
+ * @throws {Error} If swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
1046
+ * @throws {Error} If the LP refunded sooner than we were able to claim
1047
+ */
1048
+ async waitTillClaimed(maxWaitTimeSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
1049
+ if(this._state===FromBTCSwapState.CLAIM_CLAIMED) return Promise.resolve(true);
1050
+ if(this._state!==FromBTCSwapState.BTC_TX_CONFIRMED) throw new Error("Invalid state (not BTC_TX_CONFIRMED)");
1051
+
1052
+ const abortController = extendAbortController(abortSignal);
1053
+
1054
+ let timedOut: boolean = false;
1055
+ if(maxWaitTimeSeconds!=null) {
1056
+ const timeout = setTimeout(() => {
1057
+ timedOut = true;
1058
+ abortController.abort();
1059
+ }, maxWaitTimeSeconds * 1000);
1060
+ abortController.signal.addEventListener("abort", () => clearTimeout(timeout));
1061
+ }
1062
+
1063
+ let res: 0 | 1 | SwapCommitState;
1064
+ try {
1065
+ res = await Promise.race([
1066
+ this.watchdogWaitTillResult(undefined, abortController.signal),
1067
+ this.waitTillState(FromBTCSwapState.CLAIM_CLAIMED, "eq", abortController.signal).then(() => 0 as const),
1068
+ this.waitTillState(FromBTCSwapState.FAILED, "eq", abortController.signal).then(() => 1 as const),
1069
+ ]);
1070
+ abortController.abort();
1071
+ } catch (e) {
1072
+ abortController.abort();
1073
+ if(timedOut) return false;
1074
+ throw e;
1075
+ }
1076
+
1077
+ if(res===0) {
1078
+ this.logger.debug("waitTillClaimed(): Resolved from state change (CLAIM_CLAIMED)");
1079
+ return true;
1080
+ }
1081
+ if(res===1) {
1082
+ this.logger.debug("waitTillClaimed(): Resolved from state change (FAILED)");
1083
+ throw new Error("Offerer refunded during claiming");
1084
+ }
1085
+ this.logger.debug("waitTillClaimed(): Resolved from watchdog");
1086
+
1087
+ if(res?.type===SwapCommitStateType.PAID) {
1088
+ if((this._state as FromBTCSwapState)!==FromBTCSwapState.CLAIM_CLAIMED) {
1089
+ if(this._claimTxId==null) this._claimTxId = await res.getClaimTxId();
1090
+ const txId = Buffer.from(await res.getClaimResult(), "hex").reverse().toString("hex");
1091
+ await this._setBitcoinTxId(txId);
1092
+ await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
1093
+ }
1094
+ }
1095
+ if(res?.type===SwapCommitStateType.NOT_COMMITED || res?.type===SwapCommitStateType.EXPIRED) {
1096
+ if(
1097
+ (this._state as FromBTCSwapState)!==FromBTCSwapState.CLAIM_CLAIMED &&
1098
+ (this._state as FromBTCSwapState)!==FromBTCSwapState.FAILED
1099
+ ) {
1100
+ if(res.getRefundTxId!=null) this._refundTxId = await res.getRefundTxId();
1101
+ await this._saveAndEmit(FromBTCSwapState.FAILED);
1102
+ }
1103
+ throw new Error("Swap expired while waiting for claim!");
1104
+ }
1105
+
1106
+ return true;
1107
+ }
1108
+
1109
+
1110
+ //////////////////////////////
1111
+ //// Storage
1112
+
1113
+ /**
1114
+ * @inheritDoc
1115
+ */
1116
+ serialize(): any {
1117
+ return {
1118
+ ...super.serialize(),
1119
+ address: this.address,
1120
+ amount: this.amount==null ? null: this.amount.toString(10),
1121
+ requiredConfirmations: this.requiredConfirmations,
1122
+ senderAddress: this.senderAddress,
1123
+ txId: this.txId,
1124
+ vout: this.vout,
1125
+ btcTxConfirmedAt: this.btcTxConfirmedAt
1126
+ };
1127
+ }
1128
+
1129
+
1130
+ //////////////////////////////
1131
+ //// Swap ticks & sync
1132
+
1133
+ /**
1134
+ * Checks the swap's state on-chain and compares it to its internal state, updates/changes it according to on-chain
1135
+ * data
1136
+ *
1137
+ * @private
1138
+ */
1139
+ private async syncStateFromChain(quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState): Promise<boolean> {
1140
+ if(
1141
+ this._state===FromBTCSwapState.PR_CREATED ||
1142
+ this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
1143
+ this._state===FromBTCSwapState.CLAIM_COMMITED ||
1144
+ this._state===FromBTCSwapState.BTC_TX_CONFIRMED ||
1145
+ this._state===FromBTCSwapState.EXPIRED
1146
+ ) {
1147
+ let quoteExpired: boolean = false;
1148
+ if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
1149
+ quoteExpired = quoteDefinitelyExpired ?? await this._verifyQuoteDefinitelyExpired(); //Make sure we check for expiry here, to prevent race conditions
1150
+ }
1151
+
1152
+ const status = commitStatus ?? await this._contract.getCommitStatus(this._getInitiator(), this._data);
1153
+ if(status!=null && await this._forciblySetOnchainState(status)) return true;
1154
+
1155
+ if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
1156
+ if(quoteExpired) {
1157
+ this._state = FromBTCSwapState.QUOTE_EXPIRED;
1158
+ return true;
1159
+ }
1160
+ }
1161
+ }
1162
+
1163
+ return false;
1164
+ }
1165
+
1166
+ /**
1167
+ * @inheritDoc
1168
+ * @internal
1169
+ */
1170
+ _shouldFetchOnchainState(): boolean {
1171
+ return this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
1172
+ this._state===FromBTCSwapState.CLAIM_COMMITED || this._state===FromBTCSwapState.BTC_TX_CONFIRMED ||
1173
+ this._state===FromBTCSwapState.EXPIRED;
1174
+ }
1175
+
1176
+ /**
1177
+ * @inheritDoc
1178
+ * @internal
1179
+ */
1180
+ _shouldFetchExpiryStatus(): boolean {
1181
+ return this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED;
1182
+ }
1183
+
1184
+ /**
1185
+ * @inheritDoc
1186
+ * @internal
1187
+ */
1188
+ async _sync(save?: boolean, quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState): Promise<boolean> {
1189
+ const changed = await this.syncStateFromChain(quoteDefinitelyExpired, commitStatus);
1190
+ if(changed && save) await this._saveAndEmit();
1191
+ return changed;
1192
+ }
1193
+
1194
+ private btcTxLastChecked?: number;
1195
+
1196
+ /**
1197
+ * @inheritDoc
1198
+ * @internal
1199
+ */
1200
+ async _forciblySetOnchainState(status: SwapCommitState): Promise<boolean> {
1201
+ switch(status.type) {
1202
+ case SwapCommitStateType.PAID:
1203
+ if(this._claimTxId==null) this._claimTxId = await status.getClaimTxId();
1204
+ const txId = Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
1205
+ await this._setBitcoinTxId(txId);
1206
+ this._state = FromBTCSwapState.CLAIM_CLAIMED;
1207
+ return true;
1208
+ case SwapCommitStateType.NOT_COMMITED:
1209
+ if(this._refundTxId==null && status.getRefundTxId) this._refundTxId = await status.getRefundTxId();
1210
+ if(this._refundTxId!=null) {
1211
+ this._state = FromBTCSwapState.FAILED;
1212
+ return true;
1213
+ }
1214
+ break;
1215
+ case SwapCommitStateType.EXPIRED:
1216
+ if(this._refundTxId==null && status.getRefundTxId) this._refundTxId = await status.getRefundTxId();
1217
+ this._state = this._refundTxId==null ? FromBTCSwapState.QUOTE_EXPIRED : FromBTCSwapState.FAILED;
1218
+ return true;
1219
+ case SwapCommitStateType.COMMITED:
1220
+ let save: boolean = false;
1221
+ if(this._state!==FromBTCSwapState.CLAIM_COMMITED && this._state!==FromBTCSwapState.BTC_TX_CONFIRMED && this._state!==FromBTCSwapState.EXPIRED) {
1222
+ this._state = FromBTCSwapState.CLAIM_COMMITED;
1223
+ save = true;
1224
+ }
1225
+ if(this.address==null) return save;
1226
+
1227
+ this.btcTxLastChecked = Date.now();
1228
+ const res = await this.getBitcoinPayment();
1229
+ if(res!=null) {
1230
+ if(this.txId!==res.txId) {
1231
+ if(res.inputAddresses!=null) this.senderAddress = res.inputAddresses[0];
1232
+ this.txId = res.txId;
1233
+ this.vout = res.vout;
1234
+ save = true;
1235
+ }
1236
+ if(this.requiredConfirmations!=null && res.confirmations>=this.requiredConfirmations) {
1237
+ this.btcTxConfirmedAt ??= Date.now();
1238
+ this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
1239
+ save = true;
1240
+ }
1241
+ }
1242
+ return save;
1243
+ }
1244
+ return false;
1245
+ }
1246
+
1247
+ /**
1248
+ * @inheritDoc
1249
+ * @internal
1250
+ */
1251
+ async _tick(save?: boolean): Promise<boolean> {
1252
+ switch(this._state) {
1253
+ case FromBTCSwapState.PR_CREATED:
1254
+ if(this.expiry<Date.now()) {
1255
+ this._state = FromBTCSwapState.QUOTE_SOFT_EXPIRED;
1256
+ if(save) await this._saveAndEmit();
1257
+ return true;
1258
+ }
1259
+ break;
1260
+ case FromBTCSwapState.CLAIM_COMMITED:
1261
+ if(this.getTimeoutTime()<Date.now()) {
1262
+ this._state = FromBTCSwapState.EXPIRED;
1263
+ if(save) await this._saveAndEmit();
1264
+ return true;
1265
+ }
1266
+ case FromBTCSwapState.EXPIRED:
1267
+ //Check if bitcoin payment was received at least every 2 minutes
1268
+ if(this.btcTxLastChecked==null || Date.now() - this.btcTxLastChecked > 120_000) {
1269
+ if(this.address!=null) try {
1270
+ this.btcTxLastChecked = Date.now();
1271
+ const res = await this.getBitcoinPayment();
1272
+ if(res!=null) {
1273
+ let shouldSave: boolean = false;
1274
+ if(this.txId!==res.txId) {
1275
+ this.txId = res.txId;
1276
+ this.vout = res.vout;
1277
+ if(res.inputAddresses!=null) this.senderAddress = res.inputAddresses[0];
1278
+ shouldSave = true;
1279
+ }
1280
+ if(this.requiredConfirmations!=null && res.confirmations>=this.requiredConfirmations) {
1281
+ this.btcTxConfirmedAt ??= Date.now();
1282
+ this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
1283
+ if(save) await this._saveAndEmit();
1284
+ shouldSave = true;
1285
+ }
1286
+ if(shouldSave && save) await this._saveAndEmit();
1287
+ return shouldSave;
1288
+ }
1289
+ } catch (e) {
1290
+ this.logger.warn("tickSwap("+this.getIdentifierHashString()+"): ", e);
1291
+ }
1292
+ }
1293
+ break;
1294
+ }
1295
+
1296
+ return false;
1297
+ }
1298
+
1299
+ }