@atomiqlabs/sdk 8.9.1 → 8.9.2

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 (366) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +1760 -1760
  3. package/api/index.d.ts +1 -1
  4. package/api/index.js +3 -3
  5. package/dist/ApiList.d.ts +37 -37
  6. package/dist/ApiList.js +30 -30
  7. package/dist/SmartChainAssets.d.ts +181 -181
  8. package/dist/SmartChainAssets.js +181 -181
  9. package/dist/api/ApiEndpoints.d.ts +393 -393
  10. package/dist/api/ApiEndpoints.js +2 -2
  11. package/dist/api/ApiParser.d.ts +10 -10
  12. package/dist/api/ApiParser.js +134 -134
  13. package/dist/api/ApiTypes.d.ts +157 -157
  14. package/dist/api/ApiTypes.js +75 -75
  15. package/dist/api/SerializedAction.d.ts +40 -40
  16. package/dist/api/SerializedAction.js +59 -59
  17. package/dist/api/SwapperApi.d.ts +50 -50
  18. package/dist/api/SwapperApi.js +431 -431
  19. package/dist/api/index.d.ts +5 -5
  20. package/dist/api/index.js +24 -24
  21. package/dist/bitcoin/coinselect2/accumulative.d.ts +7 -7
  22. package/dist/bitcoin/coinselect2/accumulative.js +52 -52
  23. package/dist/bitcoin/coinselect2/blackjack.d.ts +7 -7
  24. package/dist/bitcoin/coinselect2/blackjack.js +38 -38
  25. package/dist/bitcoin/coinselect2/index.d.ts +20 -20
  26. package/dist/bitcoin/coinselect2/index.js +69 -69
  27. package/dist/bitcoin/coinselect2/utils.d.ts +82 -82
  28. package/dist/bitcoin/coinselect2/utils.js +158 -158
  29. package/dist/bitcoin/wallet/BitcoinWallet.d.ts +113 -113
  30. package/dist/bitcoin/wallet/BitcoinWallet.js +335 -335
  31. package/dist/bitcoin/wallet/IBitcoinWallet.d.ts +116 -116
  32. package/dist/bitcoin/wallet/IBitcoinWallet.js +21 -21
  33. package/dist/bitcoin/wallet/SingleAddressBitcoinWallet.d.ts +106 -106
  34. package/dist/bitcoin/wallet/SingleAddressBitcoinWallet.js +196 -196
  35. package/dist/enums/FeeType.d.ts +15 -15
  36. package/dist/enums/FeeType.js +19 -19
  37. package/dist/enums/SwapAmountType.d.ts +15 -15
  38. package/dist/enums/SwapAmountType.js +19 -19
  39. package/dist/enums/SwapDirection.d.ts +15 -15
  40. package/dist/enums/SwapDirection.js +19 -19
  41. package/dist/enums/SwapSide.d.ts +15 -15
  42. package/dist/enums/SwapSide.js +19 -19
  43. package/dist/enums/SwapType.d.ts +75 -75
  44. package/dist/enums/SwapType.js +79 -79
  45. package/dist/errors/IntermediaryError.d.ts +13 -13
  46. package/dist/errors/IntermediaryError.js +27 -27
  47. package/dist/errors/RequestError.d.ts +32 -32
  48. package/dist/errors/RequestError.js +54 -54
  49. package/dist/errors/UserError.d.ts +8 -8
  50. package/dist/errors/UserError.js +16 -16
  51. package/dist/events/UnifiedSwapEventListener.d.ts +24 -24
  52. package/dist/events/UnifiedSwapEventListener.js +138 -138
  53. package/dist/http/HttpUtils.d.ts +29 -29
  54. package/dist/http/HttpUtils.js +97 -97
  55. package/dist/http/paramcoders/IParamReader.d.ts +8 -8
  56. package/dist/http/paramcoders/IParamReader.js +2 -2
  57. package/dist/http/paramcoders/ParamDecoder.d.ts +44 -44
  58. package/dist/http/paramcoders/ParamDecoder.js +137 -137
  59. package/dist/http/paramcoders/ParamEncoder.d.ts +20 -20
  60. package/dist/http/paramcoders/ParamEncoder.js +36 -36
  61. package/dist/http/paramcoders/SchemaVerifier.d.ts +26 -26
  62. package/dist/http/paramcoders/SchemaVerifier.js +145 -145
  63. package/dist/http/paramcoders/client/ResponseParamDecoder.d.ts +11 -11
  64. package/dist/http/paramcoders/client/ResponseParamDecoder.js +57 -57
  65. package/dist/http/paramcoders/client/StreamParamEncoder.d.ts +13 -13
  66. package/dist/http/paramcoders/client/StreamParamEncoder.js +26 -26
  67. package/dist/http/paramcoders/client/StreamingFetchPromise.d.ts +17 -17
  68. package/dist/http/paramcoders/client/StreamingFetchPromise.js +175 -175
  69. package/dist/index.d.ts +86 -86
  70. package/dist/index.js +159 -159
  71. package/dist/intermediaries/Intermediary.d.ts +178 -178
  72. package/dist/intermediaries/Intermediary.js +166 -166
  73. package/dist/intermediaries/IntermediaryDiscovery.d.ts +216 -216
  74. package/dist/intermediaries/IntermediaryDiscovery.js +424 -424
  75. package/dist/intermediaries/apis/IntermediaryAPI.d.ts +607 -607
  76. package/dist/intermediaries/apis/IntermediaryAPI.js +764 -764
  77. package/dist/intermediaries/apis/TrustedIntermediaryAPI.d.ts +155 -155
  78. package/dist/intermediaries/apis/TrustedIntermediaryAPI.js +137 -137
  79. package/dist/intermediaries/auth/SignedKeyBasedAuth.d.ts +14 -14
  80. package/dist/intermediaries/auth/SignedKeyBasedAuth.js +68 -68
  81. package/dist/lnurl/LNURL.d.ts +102 -102
  82. package/dist/lnurl/LNURL.js +321 -321
  83. package/dist/prices/RedundantSwapPrice.d.ts +110 -110
  84. package/dist/prices/RedundantSwapPrice.js +222 -222
  85. package/dist/prices/SingleSwapPrice.d.ts +34 -34
  86. package/dist/prices/SingleSwapPrice.js +44 -44
  87. package/dist/prices/SwapPriceWithChain.d.ts +107 -107
  88. package/dist/prices/SwapPriceWithChain.js +128 -128
  89. package/dist/prices/abstract/ICachedSwapPrice.d.ts +28 -28
  90. package/dist/prices/abstract/ICachedSwapPrice.js +62 -62
  91. package/dist/prices/abstract/IPriceProvider.d.ts +81 -81
  92. package/dist/prices/abstract/IPriceProvider.js +74 -74
  93. package/dist/prices/abstract/ISwapPrice.d.ts +168 -168
  94. package/dist/prices/abstract/ISwapPrice.js +279 -279
  95. package/dist/prices/providers/BinancePriceProvider.d.ts +23 -23
  96. package/dist/prices/providers/BinancePriceProvider.js +30 -30
  97. package/dist/prices/providers/CoinGeckoPriceProvider.d.ts +23 -23
  98. package/dist/prices/providers/CoinGeckoPriceProvider.js +29 -29
  99. package/dist/prices/providers/CoinPaprikaPriceProvider.d.ts +25 -25
  100. package/dist/prices/providers/CoinPaprikaPriceProvider.js +29 -29
  101. package/dist/prices/providers/CustomPriceProvider.d.ts +24 -24
  102. package/dist/prices/providers/CustomPriceProvider.js +35 -35
  103. package/dist/prices/providers/KrakenPriceProvider.d.ts +38 -38
  104. package/dist/prices/providers/KrakenPriceProvider.js +45 -45
  105. package/dist/prices/providers/OKXPriceProvider.d.ts +34 -34
  106. package/dist/prices/providers/OKXPriceProvider.js +29 -29
  107. package/dist/prices/providers/abstract/ExchangePriceProvider.d.ts +17 -17
  108. package/dist/prices/providers/abstract/ExchangePriceProvider.js +21 -21
  109. package/dist/prices/providers/abstract/HttpPriceProvider.d.ts +7 -7
  110. package/dist/prices/providers/abstract/HttpPriceProvider.js +12 -12
  111. package/dist/storage/IUnifiedStorage.d.ts +127 -127
  112. package/dist/storage/IUnifiedStorage.js +2 -2
  113. package/dist/storage/UnifiedSwapStorage.d.ts +120 -120
  114. package/dist/storage/UnifiedSwapStorage.js +154 -154
  115. package/dist/storage-browser/IndexedDBUnifiedStorage.d.ts +63 -63
  116. package/dist/storage-browser/IndexedDBUnifiedStorage.js +298 -298
  117. package/dist/storage-browser/LocalStorageManager.d.ts +49 -49
  118. package/dist/storage-browser/LocalStorageManager.js +93 -93
  119. package/dist/swapper/Swapper.d.ts +765 -770
  120. package/dist/swapper/Swapper.js +1749 -1758
  121. package/dist/swapper/SwapperFactory.d.ts +135 -135
  122. package/dist/swapper/SwapperFactory.js +162 -162
  123. package/dist/swapper/SwapperUtils.d.ts +222 -222
  124. package/dist/swapper/SwapperUtils.js +519 -519
  125. package/dist/swapper/SwapperWithChain.d.ts +404 -404
  126. package/dist/swapper/SwapperWithChain.js +469 -469
  127. package/dist/swapper/SwapperWithSigner.d.ts +322 -322
  128. package/dist/swapper/SwapperWithSigner.js +318 -318
  129. package/dist/swaps/IAddressSwap.d.ts +22 -22
  130. package/dist/swaps/IAddressSwap.js +14 -14
  131. package/dist/swaps/IBTCWalletSwap.d.ts +73 -73
  132. package/dist/swaps/IBTCWalletSwap.js +18 -18
  133. package/dist/swaps/IClaimableSwap.d.ts +49 -49
  134. package/dist/swaps/IClaimableSwap.js +15 -15
  135. package/dist/swaps/IClaimableSwapWrapper.d.ts +15 -15
  136. package/dist/swaps/IClaimableSwapWrapper.js +2 -2
  137. package/dist/swaps/IRefundableSwap.d.ts +43 -43
  138. package/dist/swaps/IRefundableSwap.js +14 -14
  139. package/dist/swaps/ISwap.d.ts +453 -453
  140. package/dist/swaps/ISwap.js +371 -371
  141. package/dist/swaps/ISwapWithGasDrop.d.ts +21 -21
  142. package/dist/swaps/ISwapWithGasDrop.js +12 -12
  143. package/dist/swaps/ISwapWrapper.d.ts +295 -295
  144. package/dist/swaps/ISwapWrapper.js +373 -373
  145. package/dist/swaps/escrow_swaps/IEscrowSelfInitSwap.d.ts +98 -98
  146. package/dist/swaps/escrow_swaps/IEscrowSelfInitSwap.js +126 -126
  147. package/dist/swaps/escrow_swaps/IEscrowSwap.d.ts +139 -139
  148. package/dist/swaps/escrow_swaps/IEscrowSwap.js +172 -172
  149. package/dist/swaps/escrow_swaps/IEscrowSwapWrapper.d.ts +129 -129
  150. package/dist/swaps/escrow_swaps/IEscrowSwapWrapper.js +167 -167
  151. package/dist/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.d.ts +107 -107
  152. package/dist/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.js +130 -130
  153. package/dist/swaps/escrow_swaps/frombtc/IFromBTCSelfInitSwap.d.ts +162 -162
  154. package/dist/swaps/escrow_swaps/frombtc/IFromBTCSelfInitSwap.js +190 -190
  155. package/dist/swaps/escrow_swaps/frombtc/IFromBTCWrapper.d.ts +64 -64
  156. package/dist/swaps/escrow_swaps/frombtc/IFromBTCWrapper.js +82 -82
  157. package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.d.ts +547 -547
  158. package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.js +1419 -1419
  159. package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.d.ts +192 -192
  160. package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.js +432 -432
  161. package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.d.ts +650 -650
  162. package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.js +1577 -1577
  163. package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.d.ts +237 -237
  164. package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.js +525 -525
  165. package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.d.ts +491 -491
  166. package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.js +1463 -1463
  167. package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.d.ts +204 -204
  168. package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.js +406 -406
  169. package/dist/swaps/escrow_swaps/tobtc/IToBTCSwap.d.ts +446 -446
  170. package/dist/swaps/escrow_swaps/tobtc/IToBTCSwap.js +1097 -1097
  171. package/dist/swaps/escrow_swaps/tobtc/IToBTCWrapper.d.ts +68 -68
  172. package/dist/swaps/escrow_swaps/tobtc/IToBTCWrapper.js +117 -117
  173. package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNSwap.d.ts +127 -127
  174. package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNSwap.js +256 -256
  175. package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.d.ts +252 -252
  176. package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.js +535 -535
  177. package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCSwap.d.ts +73 -73
  178. package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCSwap.js +155 -155
  179. package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.d.ts +134 -134
  180. package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.js +286 -286
  181. package/dist/swaps/spv_swaps/SpvFromBTCSwap.d.ts +694 -694
  182. package/dist/swaps/spv_swaps/SpvFromBTCSwap.js +1687 -1687
  183. package/dist/swaps/spv_swaps/SpvFromBTCWrapper.d.ts +259 -259
  184. package/dist/swaps/spv_swaps/SpvFromBTCWrapper.js +947 -947
  185. package/dist/swaps/trusted/ln/LnForGasSwap.d.ts +302 -302
  186. package/dist/swaps/trusted/ln/LnForGasSwap.js +625 -625
  187. package/dist/swaps/trusted/ln/LnForGasWrapper.d.ts +40 -40
  188. package/dist/swaps/trusted/ln/LnForGasWrapper.js +82 -82
  189. package/dist/swaps/trusted/onchain/OnchainForGasSwap.d.ts +343 -343
  190. package/dist/swaps/trusted/onchain/OnchainForGasSwap.js +698 -698
  191. package/dist/swaps/trusted/onchain/OnchainForGasWrapper.d.ts +71 -71
  192. package/dist/swaps/trusted/onchain/OnchainForGasWrapper.js +93 -93
  193. package/dist/types/AmountData.d.ts +10 -10
  194. package/dist/types/AmountData.js +2 -2
  195. package/dist/types/CustomPriceFunction.d.ts +11 -11
  196. package/dist/types/CustomPriceFunction.js +2 -2
  197. package/dist/types/PriceInfoType.d.ts +28 -28
  198. package/dist/types/PriceInfoType.js +57 -57
  199. package/dist/types/SwapExecutionAction.d.ts +195 -195
  200. package/dist/types/SwapExecutionAction.js +106 -106
  201. package/dist/types/SwapExecutionStep.d.ts +144 -144
  202. package/dist/types/SwapExecutionStep.js +87 -87
  203. package/dist/types/SwapStateInfo.d.ts +5 -5
  204. package/dist/types/SwapStateInfo.js +2 -2
  205. package/dist/types/SwapWithSigner.d.ts +17 -17
  206. package/dist/types/SwapWithSigner.js +43 -43
  207. package/dist/types/Token.d.ts +99 -99
  208. package/dist/types/Token.js +76 -76
  209. package/dist/types/TokenAmount.d.ts +75 -75
  210. package/dist/types/TokenAmount.js +85 -85
  211. package/dist/types/fees/Fee.d.ts +50 -50
  212. package/dist/types/fees/Fee.js +2 -2
  213. package/dist/types/fees/FeeBreakdown.d.ts +11 -11
  214. package/dist/types/fees/FeeBreakdown.js +2 -2
  215. package/dist/types/fees/PercentagePPM.d.ts +17 -17
  216. package/dist/types/fees/PercentagePPM.js +18 -18
  217. package/dist/types/lnurl/LNURLPay.d.ts +61 -61
  218. package/dist/types/lnurl/LNURLPay.js +31 -31
  219. package/dist/types/lnurl/LNURLWithdraw.d.ts +48 -48
  220. package/dist/types/lnurl/LNURLWithdraw.js +27 -27
  221. package/dist/types/wallets/LightningInvoiceCreateService.d.ts +24 -24
  222. package/dist/types/wallets/LightningInvoiceCreateService.js +15 -15
  223. package/dist/types/wallets/MinimalBitcoinWalletInterface.d.ts +23 -23
  224. package/dist/types/wallets/MinimalBitcoinWalletInterface.js +2 -2
  225. package/dist/types/wallets/MinimalLightningNetworkWalletInterface.d.ts +9 -9
  226. package/dist/types/wallets/MinimalLightningNetworkWalletInterface.js +2 -2
  227. package/dist/utils/AutomaticClockDriftCorrection.d.ts +1 -1
  228. package/dist/utils/AutomaticClockDriftCorrection.js +70 -70
  229. package/dist/utils/BitcoinUtils.d.ts +18 -18
  230. package/dist/utils/BitcoinUtils.js +174 -174
  231. package/dist/utils/BitcoinWalletUtils.d.ts +7 -7
  232. package/dist/utils/BitcoinWalletUtils.js +14 -14
  233. package/dist/utils/Logger.d.ts +7 -7
  234. package/dist/utils/Logger.js +12 -12
  235. package/dist/utils/RetryUtils.d.ts +22 -22
  236. package/dist/utils/RetryUtils.js +67 -67
  237. package/dist/utils/SwapUtils.d.ts +88 -88
  238. package/dist/utils/SwapUtils.js +72 -72
  239. package/dist/utils/TimeoutUtils.d.ts +17 -17
  240. package/dist/utils/TimeoutUtils.js +55 -55
  241. package/dist/utils/TokenUtils.d.ts +19 -19
  242. package/dist/utils/TokenUtils.js +37 -37
  243. package/dist/utils/TypeUtils.d.ts +7 -7
  244. package/dist/utils/TypeUtils.js +2 -2
  245. package/dist/utils/Utils.d.ts +69 -69
  246. package/dist/utils/Utils.js +214 -214
  247. package/package.json +46 -46
  248. package/src/SmartChainAssets.ts +186 -186
  249. package/src/api/ApiEndpoints.ts +427 -427
  250. package/src/api/ApiParser.ts +138 -138
  251. package/src/api/ApiTypes.ts +229 -229
  252. package/src/api/SerializedAction.ts +97 -97
  253. package/src/api/SwapperApi.ts +545 -545
  254. package/src/api/index.ts +5 -5
  255. package/src/bitcoin/coinselect2/accumulative.ts +69 -69
  256. package/src/bitcoin/coinselect2/blackjack.ts +50 -50
  257. package/src/bitcoin/coinselect2/index.ts +93 -93
  258. package/src/bitcoin/coinselect2/utils.ts +236 -236
  259. package/src/bitcoin/wallet/BitcoinWallet.ts +439 -439
  260. package/src/bitcoin/wallet/IBitcoinWallet.ts +140 -140
  261. package/src/bitcoin/wallet/SingleAddressBitcoinWallet.ts +225 -225
  262. package/src/enums/FeeType.ts +15 -15
  263. package/src/enums/SwapAmountType.ts +16 -16
  264. package/src/enums/SwapDirection.ts +15 -15
  265. package/src/enums/SwapSide.ts +16 -16
  266. package/src/enums/SwapType.ts +75 -75
  267. package/src/errors/IntermediaryError.ts +28 -28
  268. package/src/errors/RequestError.ts +64 -64
  269. package/src/errors/UserError.ts +15 -15
  270. package/src/events/UnifiedSwapEventListener.ts +181 -181
  271. package/src/http/HttpUtils.ts +97 -97
  272. package/src/http/paramcoders/IParamReader.ts +9 -9
  273. package/src/http/paramcoders/ParamDecoder.ts +145 -145
  274. package/src/http/paramcoders/ParamEncoder.ts +40 -40
  275. package/src/http/paramcoders/SchemaVerifier.ts +153 -153
  276. package/src/http/paramcoders/client/ResponseParamDecoder.ts +57 -57
  277. package/src/http/paramcoders/client/StreamParamEncoder.ts +28 -28
  278. package/src/http/paramcoders/client/StreamingFetchPromise.ts +194 -194
  279. package/src/index.ts +141 -141
  280. package/src/intermediaries/Intermediary.ts +280 -280
  281. package/src/intermediaries/IntermediaryDiscovery.ts +548 -548
  282. package/src/intermediaries/apis/IntermediaryAPI.ts +1247 -1247
  283. package/src/intermediaries/auth/SignedKeyBasedAuth.ts +69 -69
  284. package/src/lnurl/LNURL.ts +402 -402
  285. package/src/prices/RedundantSwapPrice.ts +264 -264
  286. package/src/prices/SingleSwapPrice.ts +50 -50
  287. package/src/prices/SwapPriceWithChain.ts +194 -194
  288. package/src/prices/abstract/ICachedSwapPrice.ts +85 -85
  289. package/src/prices/abstract/IPriceProvider.ts +127 -127
  290. package/src/prices/abstract/ISwapPrice.ts +390 -390
  291. package/src/prices/providers/BinancePriceProvider.ts +48 -48
  292. package/src/prices/providers/CoinGeckoPriceProvider.ts +46 -46
  293. package/src/prices/providers/CoinPaprikaPriceProvider.ts +49 -49
  294. package/src/prices/providers/CustomPriceProvider.ts +40 -40
  295. package/src/prices/providers/KrakenPriceProvider.ts +83 -83
  296. package/src/prices/providers/OKXPriceProvider.ts +59 -59
  297. package/src/prices/providers/abstract/ExchangePriceProvider.ts +31 -31
  298. package/src/prices/providers/abstract/HttpPriceProvider.ts +14 -14
  299. package/src/storage/IUnifiedStorage.ts +136 -136
  300. package/src/storage/UnifiedSwapStorage.ts +175 -175
  301. package/src/storage-browser/IndexedDBUnifiedStorage.ts +350 -350
  302. package/src/storage-browser/LocalStorageManager.ts +106 -106
  303. package/src/swapper/Swapper.ts +2557 -2570
  304. package/src/swapper/SwapperFactory.ts +307 -307
  305. package/src/swapper/SwapperUtils.ts +610 -610
  306. package/src/swapper/SwapperWithChain.ts +707 -707
  307. package/src/swapper/SwapperWithSigner.ts +511 -511
  308. package/src/swaps/IAddressSwap.ts +30 -30
  309. package/src/swaps/IBTCWalletSwap.ts +92 -92
  310. package/src/swaps/IClaimableSwap.ts +65 -65
  311. package/src/swaps/IClaimableSwapWrapper.ts +17 -17
  312. package/src/swaps/IRefundableSwap.ts +58 -58
  313. package/src/swaps/ISwap.ts +775 -775
  314. package/src/swaps/ISwapWithGasDrop.ts +25 -25
  315. package/src/swaps/ISwapWrapper.ts +564 -564
  316. package/src/swaps/escrow_swaps/IEscrowSelfInitSwap.ts +217 -217
  317. package/src/swaps/escrow_swaps/IEscrowSwap.ts +271 -271
  318. package/src/swaps/escrow_swaps/IEscrowSwapWrapper.ts +284 -284
  319. package/src/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.ts +172 -172
  320. package/src/swaps/escrow_swaps/frombtc/IFromBTCSelfInitSwap.ts +300 -300
  321. package/src/swaps/escrow_swaps/frombtc/IFromBTCWrapper.ts +107 -107
  322. package/src/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.ts +1670 -1671
  323. package/src/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.ts +603 -603
  324. package/src/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.ts +1883 -1883
  325. package/src/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.ts +752 -752
  326. package/src/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.ts +1753 -1753
  327. package/src/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.ts +612 -612
  328. package/src/swaps/escrow_swaps/tobtc/IToBTCSwap.ts +1327 -1327
  329. package/src/swaps/escrow_swaps/tobtc/IToBTCWrapper.ts +138 -138
  330. package/src/swaps/escrow_swaps/tobtc/ln/ToBTCLNSwap.ts +304 -304
  331. package/src/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.ts +787 -787
  332. package/src/swaps/escrow_swaps/tobtc/onchain/ToBTCSwap.ts +206 -206
  333. package/src/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.ts +403 -403
  334. package/src/swaps/spv_swaps/SpvFromBTCSwap.ts +2148 -2148
  335. package/src/swaps/spv_swaps/SpvFromBTCWrapper.ts +1238 -1238
  336. package/src/swaps/trusted/ln/LnForGasSwap.ts +753 -753
  337. package/src/swaps/trusted/ln/LnForGasWrapper.ts +90 -90
  338. package/src/swaps/trusted/onchain/OnchainForGasSwap.ts +843 -843
  339. package/src/swaps/trusted/onchain/OnchainForGasWrapper.ts +133 -133
  340. package/src/types/AmountData.ts +9 -9
  341. package/src/types/CustomPriceFunction.ts +11 -11
  342. package/src/types/PriceInfoType.ts +66 -66
  343. package/src/types/SwapExecutionAction.ts +323 -323
  344. package/src/types/SwapExecutionStep.ts +224 -224
  345. package/src/types/SwapStateInfo.ts +6 -6
  346. package/src/types/SwapWithSigner.ts +61 -61
  347. package/src/types/Token.ts +163 -163
  348. package/src/types/TokenAmount.ts +167 -167
  349. package/src/types/fees/Fee.ts +56 -56
  350. package/src/types/fees/FeeBreakdown.ts +11 -11
  351. package/src/types/fees/PercentagePPM.ts +26 -26
  352. package/src/types/lnurl/LNURLPay.ts +79 -79
  353. package/src/types/lnurl/LNURLWithdraw.ts +61 -61
  354. package/src/types/wallets/LightningInvoiceCreateService.ts +30 -30
  355. package/src/types/wallets/MinimalBitcoinWalletInterface.ts +21 -21
  356. package/src/types/wallets/MinimalLightningNetworkWalletInterface.ts +9 -9
  357. package/src/utils/AutomaticClockDriftCorrection.ts +71 -71
  358. package/src/utils/BitcoinUtils.ts +164 -164
  359. package/src/utils/BitcoinWalletUtils.ts +15 -15
  360. package/src/utils/Logger.ts +14 -14
  361. package/src/utils/RetryUtils.ts +78 -78
  362. package/src/utils/SwapUtils.ts +99 -99
  363. package/src/utils/TimeoutUtils.ts +49 -49
  364. package/src/utils/TokenUtils.ts +33 -33
  365. package/src/utils/TypeUtils.ts +8 -8
  366. package/src/utils/Utils.ts +221 -221
@@ -1,1753 +1,1753 @@
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, getSenderAddress, getVoutIndex,
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 {timeoutPromise} from "../../../../utils/TimeoutUtils";
37
- import {toBitcoinWallet} from "../../../../utils/BitcoinWalletUtils";
38
- import {
39
- SwapExecutionActionSendToAddress,
40
- SwapExecutionActionSignPSBT,
41
- SwapExecutionActionSignSmartChainTx,
42
- SwapExecutionActionWait
43
- } from "../../../../types/SwapExecutionAction";
44
- import {
45
- SwapExecutionStepPayment,
46
- SwapExecutionStepSettlement,
47
- SwapExecutionStepSetup
48
- } from "../../../../types/SwapExecutionStep";
49
- import {SwapStateInfo} from "../../../../types/SwapStateInfo";
50
-
51
- /**
52
- * State enum for legacy escrow based Bitcoin -> Smart chain swaps.
53
- *
54
- * @category Swaps/Legacy/Bitcoin → Smart chain
55
- */
56
- export enum FromBTCSwapState {
57
- /**
58
- * Bitcoin swap address has expired and the intermediary (LP) has already refunded
59
- * its funds. No BTC should be sent anymore!
60
- */
61
- FAILED = -4,
62
- /**
63
- * Bitcoin swap address has expired, user should not send any BTC anymore! Though
64
- * the intermediary (LP) hasn't refunded yet. So if there is a transaction already
65
- * in-flight the swap might still succeed.
66
- */
67
- EXPIRED = -3,
68
- /**
69
- * Swap has expired for good and there is no way how it can be executed anymore
70
- */
71
- QUOTE_EXPIRED = -2,
72
- /**
73
- * A swap is almost expired, and it should be presented to the user as expired, though
74
- * there is still a chance that it will be processed
75
- */
76
- QUOTE_SOFT_EXPIRED = -1,
77
- /**
78
- * Swap quote was created, use the {@link FromBTCSwap.commit} or {@link FromBTCSwap.txsCommit} functions
79
- * to initiate it by creating the swap escrow on the destination smart chain
80
- */
81
- PR_CREATED = 0,
82
- /**
83
- * Swap escrow was initiated (committed) on the destination chain, user can send the BTC to the
84
- * swap address with the {@link FromBTCSwap.getFundedPsbt}, {@link FromBTCSwap.getAddress} or
85
- * {@link FromBTCSwap.getHyperlink} functions.
86
- */
87
- CLAIM_COMMITED = 1,
88
- /**
89
- * Input bitcoin transaction was confirmed, wait for automatic settlement by the watchtowers
90
- * using the {@link FromBTCSwap.waitTillClaimed} function or settle manually using the {@link FromBTCSwap.claim}
91
- * or {@link FromBTCSwap.txsClaim} function.
92
- */
93
- BTC_TX_CONFIRMED = 2,
94
- /**
95
- * Swap successfully settled and funds received on the destination chain
96
- */
97
- CLAIM_CLAIMED = 3
98
- }
99
-
100
- const FromBTCSwapStateDescription = {
101
- [FromBTCSwapState.FAILED]: "Bitcoin swap address has expired and the intermediary (LP) has already refunded its funds. No BTC should be sent anymore!",
102
- [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.",
103
- [FromBTCSwapState.QUOTE_EXPIRED]: "Swap has expired for good and there is no way how it can be executed anymore",
104
- [FromBTCSwapState.QUOTE_SOFT_EXPIRED]: "The swap is expired, though there is still a chance that it will be processed",
105
- [FromBTCSwapState.PR_CREATED]: "Swap quote was created, initiate it by creating the swap escrow on the destination smart chain",
106
- [FromBTCSwapState.CLAIM_COMMITED]: "Swap escrow was initiated (committed) on the destination chain, user can send the BTC to the Bitcoin swap address.",
107
- [FromBTCSwapState.BTC_TX_CONFIRMED]: "Input bitcoin transaction was confirmed, wait for automatic settlement by the watchtower or settle manually.",
108
- [FromBTCSwapState.CLAIM_CLAIMED]: "Swap successfully settled and funds received on the destination chain"
109
- };
110
-
111
- export type FromBTCSwapInit<T extends SwapData> = IEscrowSelfInitSwapInit<T> & {
112
- data: T;
113
- address?: string;
114
- amount?: bigint;
115
- requiredConfirmations?: number;
116
- };
117
-
118
- export function isFromBTCSwapInit<T extends SwapData>(obj: any): obj is FromBTCSwapInit<T> {
119
- return typeof(obj.data) === "object" &&
120
- (obj.address==null || typeof(obj.address) === "string") &&
121
- (obj.amount==null || typeof(obj.amount) === "bigint") &&
122
- (obj.requiredConfirmations==null || typeof(obj.requiredConfirmations) === "number") &&
123
- isIEscrowSelfInitSwapInit<T>(obj);
124
- }
125
-
126
- /**
127
- * Legacy escrow (PrTLC) based swap for Bitcoin -> Smart chains, requires manual initiation
128
- * of the swap escrow on the destination chain.
129
- *
130
- * @category Swaps/Legacy/Bitcoin → Smart chain
131
- */
132
- export class FromBTCSwap<T extends ChainType = ChainType>
133
- extends IFromBTCSelfInitSwap<T, FromBTCDefinition<T>, FromBTCSwapState>
134
- implements IBTCWalletSwap, IClaimableSwap<T, FromBTCDefinition<T>, FromBTCSwapState>, IAddressSwap {
135
-
136
- protected readonly TYPE: SwapType.FROM_BTC = SwapType.FROM_BTC;
137
- /**
138
- * @internal
139
- */
140
- protected readonly swapStateName = (state: number) => FromBTCSwapState[state];
141
- /**
142
- * @internal
143
- */
144
- protected readonly swapStateDescription = FromBTCSwapStateDescription;
145
- /**
146
- * @internal
147
- */
148
- protected readonly logger: LoggerType;
149
- /**
150
- * @internal
151
- */
152
- protected readonly inputToken: BtcToken<false> = BitcoinTokens.BTC;
153
- /**
154
- * @internal
155
- */
156
- protected readonly feeRate!: string;
157
-
158
- /**
159
- * @internal
160
- */
161
- readonly _data!: T["Data"];
162
-
163
- private address?: string;
164
- private amount?: bigint;
165
- private requiredConfirmations?: number;
166
-
167
- private senderAddress?: string;
168
- private txId?: string;
169
- private vout?: number;
170
-
171
- private btcTxConfirmedAt?: number;
172
-
173
- constructor(wrapper: FromBTCWrapper<T>, init: FromBTCSwapInit<T["Data"]>);
174
- constructor(wrapper: FromBTCWrapper<T>, obj: any);
175
- constructor(wrapper: FromBTCWrapper<T>, initOrObject: FromBTCSwapInit<T["Data"]> | any) {
176
- if(isFromBTCSwapInit(initOrObject) && initOrObject.url!=null) initOrObject.url += "/frombtc";
177
- super(wrapper, initOrObject);
178
- if(isFromBTCSwapInit(initOrObject)) {
179
- this._state = FromBTCSwapState.PR_CREATED;
180
- this._data = initOrObject.data;
181
- this.feeRate = initOrObject.feeRate;
182
- this.address = initOrObject.address;
183
- this.amount = initOrObject.amount;
184
- this.requiredConfirmations = initOrObject.requiredConfirmations;
185
- } else {
186
- this.address = initOrObject.address;
187
- this.amount = toBigInt(initOrObject.amount);
188
- this.senderAddress = initOrObject.senderAddress;
189
- this.txId = initOrObject.txId;
190
- this.vout = initOrObject.vout;
191
- this.requiredConfirmations = initOrObject.requiredConfirmations ?? this._data.getConfirmationsHint();
192
- this.btcTxConfirmedAt = initOrObject.btcTxConfirmedAt;
193
- }
194
- this.tryRecomputeSwapPrice();
195
- this.logger = getLogger("FromBTC("+this.getIdentifierHashString()+"): ");
196
- }
197
-
198
- /**
199
- * @inheritDoc
200
- * @internal
201
- */
202
- protected getSwapData(): T["Data"] {
203
- return this._data;
204
- }
205
-
206
- /**
207
- * @inheritDoc
208
- * @internal
209
- */
210
- protected upgradeVersion() {
211
- if(this.version == null) {
212
- switch(this._state) {
213
- case -2:
214
- this._state = FromBTCSwapState.FAILED
215
- break;
216
- case -1:
217
- this._state = FromBTCSwapState.QUOTE_EXPIRED
218
- break;
219
- case 0:
220
- this._state = FromBTCSwapState.PR_CREATED
221
- break;
222
- case 1:
223
- this._state = FromBTCSwapState.CLAIM_COMMITED
224
- break;
225
- case 2:
226
- this._state = FromBTCSwapState.BTC_TX_CONFIRMED
227
- break;
228
- case 3:
229
- this._state = FromBTCSwapState.CLAIM_CLAIMED
230
- break;
231
- }
232
- this.version = 1;
233
- }
234
- }
235
-
236
-
237
- //////////////////////////////
238
- //// Getters & utils
239
-
240
- /**
241
- * Returns bitcoin address where the on-chain BTC should be sent to
242
- */
243
- getAddress(): string {
244
- if(this._state===FromBTCSwapState.PR_CREATED) throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
245
- return this.address ?? "";
246
- }
247
-
248
- /**
249
- * Unsafe bitcoin hyperlink getter, returns the address even before the swap is committed!
250
- *
251
- * @private
252
- */
253
- private _getHyperlink(): string {
254
- return this.address==null || this.amount==null ? "" : "bitcoin:"+this.address+"?amount="+encodeURIComponent((Number(this.amount) / 100000000).toString(10));
255
- }
256
-
257
- /**
258
- * @inheritDoc
259
- */
260
- getHyperlink(): string {
261
- if(this._state===FromBTCSwapState.PR_CREATED) throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
262
- return this._getHyperlink();
263
- }
264
-
265
- /**
266
- * @inheritDoc
267
- */
268
- getInputAddress(): string | null {
269
- return this.senderAddress ?? null;
270
- }
271
-
272
- /**
273
- * @inheritDoc
274
- */
275
- getInputTxId(): string | null {
276
- return this.txId ?? null;
277
- }
278
-
279
- private async _setSubmittedBitcoinTx(txId: string, psbt?: Transaction): Promise<void> {
280
- let changed = false;
281
- if(this.txId!==txId) {
282
- this.txId = txId;
283
- changed = true;
284
- }
285
-
286
- const submittedVout = this.address==null || this.amount==null || psbt==null
287
- ? undefined
288
- : getVoutIndex(psbt, this.wrapper._options.bitcoinNetwork, this.address, this.amount);
289
- if(submittedVout!=null && this.vout!==submittedVout) {
290
- this.vout = submittedVout;
291
- changed = true;
292
- }
293
-
294
- const submittedSenderAddress = psbt==null
295
- ? undefined
296
- : getSenderAddress(psbt, this.wrapper._options.bitcoinNetwork);
297
- if(submittedSenderAddress!=null && this.senderAddress!==submittedSenderAddress) {
298
- this.senderAddress = submittedSenderAddress;
299
- changed = true;
300
- }
301
-
302
- if(changed) await this._saveAndEmit();
303
- }
304
-
305
- /**
306
- * Returns timeout time (in UNIX milliseconds) when the on-chain address will expire and no funds should be sent
307
- * to that address anymore
308
- */
309
- getTimeoutTime(): number {
310
- return Number(this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations ?? 6)) * 1000;
311
- }
312
-
313
- /**
314
- * @inheritDoc
315
- */
316
- requiresAction(): boolean {
317
- return this.isClaimable() || (this._state===FromBTCSwapState.CLAIM_COMMITED && this.getTimeoutTime()>Date.now() && this.txId==null);
318
- }
319
-
320
- /**
321
- * @inheritDoc
322
- */
323
- isFinished(): boolean {
324
- return this._state===FromBTCSwapState.CLAIM_CLAIMED || this._state===FromBTCSwapState.QUOTE_EXPIRED || this._state===FromBTCSwapState.FAILED;
325
- }
326
-
327
- /**
328
- * @inheritDoc
329
- */
330
- isClaimable(): boolean {
331
- return this._state===FromBTCSwapState.BTC_TX_CONFIRMED;
332
- }
333
-
334
- /**
335
- * @inheritDoc
336
- */
337
- isSuccessful(): boolean {
338
- return this._state===FromBTCSwapState.CLAIM_CLAIMED;
339
- }
340
-
341
- /**
342
- * @inheritDoc
343
- */
344
- isFailed(): boolean {
345
- return this._state===FromBTCSwapState.FAILED || this._state===FromBTCSwapState.EXPIRED;
346
- }
347
-
348
- /**
349
- * @inheritDoc
350
- */
351
- isInProgress(): boolean {
352
- return this._state===FromBTCSwapState.CLAIM_COMMITED ||
353
- this._state===FromBTCSwapState.BTC_TX_CONFIRMED;
354
- }
355
-
356
- /**
357
- * @inheritDoc
358
- */
359
- isQuoteExpired(): boolean {
360
- return this._state===FromBTCSwapState.QUOTE_EXPIRED;
361
- }
362
-
363
- /**
364
- * @inheritDoc
365
- */
366
- isQuoteSoftExpired(): boolean {
367
- return this._state===FromBTCSwapState.QUOTE_EXPIRED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED;
368
- }
369
-
370
- /**
371
- * @inheritDoc
372
- * @internal
373
- */
374
- protected canCommit(skipQuoteExpiryChecks?: boolean): boolean {
375
- if(this._state!==FromBTCSwapState.PR_CREATED && (!skipQuoteExpiryChecks || this._state!==FromBTCSwapState.QUOTE_SOFT_EXPIRED)) return false;
376
- if(this.requiredConfirmations==null) return false;
377
- const expiry = this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations);
378
- const currentTimestamp = BigInt(Math.floor(Date.now()/1000));
379
-
380
- return (expiry - currentTimestamp) >= this.wrapper._options.minSendWindow;
381
- }
382
-
383
-
384
- //////////////////////////////
385
- //// Amounts & fees
386
-
387
- /**
388
- * @inheritDoc
389
- */
390
- getInputToken(): BtcToken<false> {
391
- return BitcoinTokens.BTC;
392
- }
393
-
394
- /**
395
- * @inheritDoc
396
- */
397
- getInput(): TokenAmount<BtcToken<false>> {
398
- return toTokenAmount(this.amount ?? null, this.inputToken, this.wrapper._prices);
399
- }
400
-
401
- /**
402
- * Returns claimer bounty, acting as a reward for watchtowers to claim the swap automatically,
403
- * this amount is pre-funded by the user on the destination chain when the swap escrow
404
- * is initiated. For total pre-funded deposit amount see {@link getTotalDeposit}.
405
- */
406
- getClaimerBounty(): TokenAmount<SCToken<T["ChainId"]>, true> {
407
- return toTokenAmount(this._data.getClaimerBounty(), this.wrapper._tokens[this._data.getDepositToken()], this.wrapper._prices);
408
- }
409
-
410
-
411
- //////////////////////////////
412
- //// Bitcoin tx
413
-
414
- /**
415
- * If the required number of confirmations is not known, this function tries to infer it by looping through
416
- * possible confirmation targets and comparing the claim hashes
417
- *
418
- * @param btcTx Bitcoin transaction
419
- * @param vout Output index of the desired output in the bitcoin transaction
420
- *
421
- * @private
422
- */
423
- private inferRequiredConfirmationsCount(btcTx: Omit<BtcTxWithBlockheight, "hex" | "raw">, vout: number): number | undefined {
424
- const txOut = btcTx.outs[vout];
425
- for(let i=1;i<=20;i++) {
426
- const computedClaimHash = this._contract.getHashForOnchain(
427
- Buffer.from(txOut.scriptPubKey.hex, "hex"),
428
- BigInt(txOut.value),
429
- i
430
- );
431
- if(computedClaimHash.toString("hex")===this._data.getClaimHash()) {
432
- return i;
433
- }
434
- }
435
- }
436
-
437
- /**
438
- * @inheritDoc
439
- */
440
- getRequiredConfirmationsCount(): number {
441
- return this.requiredConfirmations ?? NaN;
442
- }
443
-
444
- /**
445
- * Checks whether a bitcoin payment was already made, returns the payment or `null` when no payment has been made.
446
- *
447
- * @internal
448
- */
449
- protected async getBitcoinPayment(): Promise<{
450
- txId: string,
451
- vout: number,
452
- confirmations: number,
453
- targetConfirmations: number,
454
- inputAddresses?: string[]
455
- } | null> {
456
- const txoHashHint = this._data.getTxoHashHint();
457
- if(txoHashHint==null) throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
458
- 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.");
459
-
460
- const result = await this.wrapper._btcRpc.checkAddressTxos(this.address, Buffer.from(txoHashHint, "hex"));
461
- if(result==null) return null;
462
-
463
- if(this.requiredConfirmations==null) {
464
- this.requiredConfirmations = this.inferRequiredConfirmationsCount(result.tx, result.vout);
465
- }
466
-
467
- return {
468
- inputAddresses: result.tx.inputAddresses,
469
- txId: result.tx.txid,
470
- vout: result.vout,
471
- confirmations: result.tx.confirmations ?? 0,
472
- targetConfirmations: this.getRequiredConfirmationsCount()
473
- }
474
- }
475
-
476
- /**
477
- * Used to set the txId of the bitcoin payment from the on-chain events listener
478
- *
479
- * @param txId Transaction ID that settled the swap on the smart chain
480
- *
481
- * @internal
482
- */
483
- async _setBitcoinTxId(txId: string) {
484
- if(this.txId!==txId || this.address==null || this.vout==null || this.senderAddress==null || this.amount==null) {
485
- const btcTx = await this.wrapper._btcRpc.getTransaction(txId);
486
- if(btcTx==null) return;
487
-
488
- const txoHashHint = this._data.getTxoHashHint();
489
- if(txoHashHint!=null) {
490
- const expectedTxoHash = Buffer.from(txoHashHint, "hex");
491
- const vout = btcTx.outs.findIndex(out => getTxoHash(out.scriptPubKey.hex, out.value).equals(expectedTxoHash));
492
- if(vout!==-1) {
493
- this.vout = vout;
494
- //If amount or address are not known, parse them from the bitcoin tx
495
- // this can happen if the swap is recovered from on-chain data and
496
- // hence doesn't contain the address and amount data
497
- if(this.amount==null) this.amount = BigInt(btcTx.outs[vout].value);
498
- if(this.address==null) try {
499
- this.address = fromOutputScript(this.wrapper._options.bitcoinNetwork, btcTx.outs[vout].scriptPubKey.hex);
500
- } catch (e: any) {
501
- this.logger.warn("_setBitcoinTxId(): Failed to parse address from output script: ", e);
502
- }
503
- if(this.requiredConfirmations==null) {
504
- this.requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
505
- }
506
- }
507
- }
508
-
509
- if(btcTx.inputAddresses!=null) {
510
- this.senderAddress = btcTx.inputAddresses[0];
511
- }
512
- }
513
-
514
- this.txId = txId;
515
- }
516
-
517
- /**
518
- * @inheritDoc
519
- *
520
- * @throws {Error} if in invalid state (must be {@link FromBTCSwapState.CLAIM_COMMITED})
521
- */
522
- async waitForBitcoinTransaction(
523
- updateCallback?: (txId?: string, confirmations?: number, targetConfirmations?: number, txEtaMs?: number) => void,
524
- checkIntervalSeconds?: number,
525
- abortSignal?: AbortSignal
526
- ): Promise<string> {
527
- if(this._state!==FromBTCSwapState.CLAIM_COMMITED && this._state!==FromBTCSwapState.EXPIRED) throw new Error("Must be in COMMITED state!");
528
- const txoHashHint = this._data.getTxoHashHint();
529
- if(txoHashHint==null) throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
530
- 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.");
531
-
532
- let abortedDueToEnoughConfirmationsResult: {
533
- tx: Omit<BtcTxWithBlockheight, "hex" | "raw">, vout: number
534
- } | undefined;
535
- const abortController = extendAbortController(abortSignal);
536
-
537
- const result = await this.wrapper._btcRpc.waitForAddressTxo(
538
- this.address,
539
- Buffer.from(txoHashHint, "hex"),
540
- this.requiredConfirmations ?? 6, //In case confirmation count is not known, we use a conservative estimate
541
- (btcTx?: Omit<BtcTxWithBlockheight, "hex" | "raw">, vout?: number, txEtaMs?: number) => {
542
- let requiredConfirmations = this.requiredConfirmations;
543
-
544
- if(btcTx!=null && vout!=null && requiredConfirmations==null) {
545
- requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
546
- }
547
-
548
- if(btcTx!=null && (
549
- btcTx.txid!==this.txId ||
550
- this.vout==null ||
551
- this.senderAddress==null ||
552
- (this.requiredConfirmations==null && requiredConfirmations!=null)
553
- )) {
554
- this.txId = btcTx.txid;
555
- this.vout = vout;
556
- this.requiredConfirmations = requiredConfirmations;
557
- if(btcTx.inputAddresses!=null) this.senderAddress = btcTx.inputAddresses[0];
558
- this._saveAndEmit().catch(e => {
559
- this.logger.error("waitForBitcoinTransaction(): Failed to save swap from within waitForAddressTxo callback:", e)
560
- });
561
- }
562
-
563
- //Abort the loop as soon as the transaction gets enough confirmations, this is required in case
564
- // we pass a default 6 confirmations to the fn, but then are able to infer the actual confirmation
565
- // target from the prior block
566
- if(btcTx?.confirmations!=null && requiredConfirmations!=null && requiredConfirmations<=btcTx.confirmations && vout!=null) {
567
- abortedDueToEnoughConfirmationsResult = {
568
- tx: btcTx,
569
- vout
570
- };
571
- abortController.abort();
572
- return;
573
- }
574
-
575
- if(updateCallback!=null) updateCallback(btcTx?.txid, btcTx==null ? undefined : (btcTx?.confirmations ?? 0), requiredConfirmations ?? NaN, txEtaMs);
576
- },
577
- abortController.signal,
578
- checkIntervalSeconds
579
- ).catch(e => {
580
- //We catch the case when the loop was aborted due to the transaction getting enough confirmations
581
- if(abortedDueToEnoughConfirmationsResult!=null) return abortedDueToEnoughConfirmationsResult;
582
- throw e;
583
- });
584
-
585
- if(abortSignal!=null) abortSignal.throwIfAborted();
586
-
587
- this.txId = result.tx.txid;
588
- this.vout = result.vout;
589
- if(result.tx.inputAddresses!=null) this.senderAddress = result.tx.inputAddresses[0];
590
-
591
- if(
592
- (this._state as FromBTCSwapState)!==FromBTCSwapState.CLAIM_CLAIMED &&
593
- (this._state as FromBTCSwapState)!==FromBTCSwapState.FAILED
594
- ) {
595
- this.btcTxConfirmedAt ??= Date.now();
596
- this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
597
- }
598
-
599
- await this._saveAndEmit();
600
-
601
- return result.tx.txid;
602
- }
603
-
604
- /**
605
- * Private getter of the funded PSBT that doesn't check current state
606
- *
607
- * @param _bitcoinWallet Bitcoin wallet to fund the PSBT with
608
- * @param feeRate Optional bitcoin fee rate in sats/vB
609
- * @param additionalOutputs Optional additional outputs that should also be included in the generated PSBT
610
- *
611
- * @private
612
- */
613
- private async _getFundedPsbt(
614
- _bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface,
615
- feeRate?: number,
616
- additionalOutputs?: ({amount: bigint, outputScript: Uint8Array} | {amount: bigint, address: string})[]
617
- ): Promise<{psbt: Transaction, psbtHex: string, psbtBase64: string, signInputs: number[], feeRate: number}> {
618
- 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.");
619
-
620
- let bitcoinWallet: IBitcoinWallet;
621
- if(isIBitcoinWallet(_bitcoinWallet)) {
622
- bitcoinWallet = _bitcoinWallet;
623
- } else {
624
- bitcoinWallet = new SingleAddressBitcoinWallet(this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork, _bitcoinWallet);
625
- }
626
- //TODO: Maybe re-introduce fee rate check here if passed from the user
627
- if(feeRate==null) {
628
- feeRate = await bitcoinWallet.getFeeRate();
629
- }
630
-
631
- const basePsbt = new Transaction({
632
- allowUnknownOutputs: true,
633
- allowLegacyWitnessUtxo: true
634
- });
635
- basePsbt.addOutput({
636
- amount: this.amount,
637
- script: toOutputScript(this.wrapper._options.bitcoinNetwork, this.address)
638
- });
639
- if(additionalOutputs!=null) additionalOutputs.forEach(output => {
640
- basePsbt.addOutput({
641
- amount: output.amount,
642
- script: (output as {outputScript: Uint8Array}).outputScript ?? toOutputScript(this.wrapper._options.bitcoinNetwork, (output as {address: string}).address)
643
- });
644
- });
645
-
646
- const psbt = await bitcoinWallet.fundPsbt(basePsbt, feeRate);
647
- //Sign every input
648
- const signInputs: number[] = [];
649
- for(let i=0;i<psbt.inputsLength;i++) {
650
- signInputs.push(i);
651
- }
652
- const serializedPsbt = Buffer.from(psbt.toPSBT());
653
- return {
654
- psbt,
655
- psbtHex: serializedPsbt.toString("hex"),
656
- psbtBase64: serializedPsbt.toString("base64"),
657
- signInputs,
658
- feeRate
659
- };
660
- }
661
-
662
- /**
663
- * @inheritDoc
664
- */
665
- getFundedPsbt(
666
- _bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface,
667
- feeRate?: number,
668
- additionalOutputs?: ({amount: bigint, outputScript: Uint8Array} | {amount: bigint, address: string})[]
669
- ) {
670
- if(this._state!==FromBTCSwapState.CLAIM_COMMITED)
671
- throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
672
- if(this.txId!=null)
673
- throw new Error("Bitcoin transaction already submitted for this swap!");
674
- return this._getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs);
675
- }
676
-
677
- /**
678
- * @inheritDoc
679
- *
680
- * @throws {Error} if the swap is in invalid state (not in {@link FromBTCSwapState.CLAIM_COMMITED}), or if
681
- * the swap bitcoin address already expired.
682
- */
683
- async submitPsbt(_psbt: Transaction | string): Promise<string> {
684
- const psbt = parsePsbtTransaction(_psbt);
685
- if(this._state!==FromBTCSwapState.CLAIM_COMMITED)
686
- throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
687
- if(this.txId!=null)
688
- throw new Error("Bitcoin transaction already submitted for this swap!");
689
-
690
- //Ensure not expired
691
- if(this.getTimeoutTime()<Date.now()) {
692
- throw new Error("Swap address expired!");
693
- }
694
-
695
- const output0 = psbt.getOutput(0);
696
- if(this.amount!=null && output0.amount!==this.amount)
697
- throw new Error("PSBT output amount invalid, expected: "+this.amount+" got: "+output0.amount);
698
- if(this.address!=null) {
699
- const expectedOutputScript = toOutputScript(this.wrapper._options.bitcoinNetwork, this.address);
700
- if(output0.script==null || !expectedOutputScript.equals(output0.script))
701
- throw new Error("PSBT output script invalid!");
702
- }
703
-
704
- if(!psbt.isFinal) psbt.finalize();
705
-
706
- const txId = await this.wrapper._btcRpc.sendRawTransaction(Buffer.from(psbt.toBytes(true, true)).toString("hex"));
707
- await this._setSubmittedBitcoinTx(txId, psbt);
708
- return txId;
709
- }
710
-
711
- /**
712
- * @inheritDoc
713
- */
714
- async estimateBitcoinFee(_bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface, feeRate?: number): Promise<TokenAmount<BtcToken<false>, true> | null> {
715
- if(this.address==null || this.amount==null) return null;
716
- const bitcoinWallet: IBitcoinWallet = toBitcoinWallet(_bitcoinWallet, this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork);
717
- const txFee = await bitcoinWallet.getTransactionFee(this.address, this.amount, feeRate);
718
- if(txFee==null) return null;
719
- return toTokenAmount(BigInt(txFee), BitcoinTokens.BTC, this.wrapper._prices);
720
- }
721
-
722
- /**
723
- * @inheritDoc
724
- */
725
- async sendBitcoinTransaction(wallet: IBitcoinWallet | MinimalBitcoinWalletInterfaceWithSigner, feeRate?: number): Promise<string> {
726
- 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.");
727
-
728
- if(this._state!==FromBTCSwapState.CLAIM_COMMITED)
729
- throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
730
- if(this.txId!=null)
731
- throw new Error("Bitcoin transaction already submitted for this swap!");
732
-
733
- //Ensure not expired
734
- if(this.getTimeoutTime()<Date.now()) {
735
- throw new Error("Swap address expired!");
736
- }
737
-
738
- if(isIBitcoinWallet(wallet)) {
739
- const txId = await wallet.sendTransaction(this.address, this.amount, feeRate);
740
- await this._setSubmittedBitcoinTx(txId);
741
- return txId;
742
- } else {
743
- const {psbt, psbtHex, psbtBase64, signInputs} = await this.getFundedPsbt(wallet, feeRate);
744
- const signedPsbt = await wallet.signPsbt({
745
- psbt, psbtHex, psbtBase64
746
- }, signInputs);
747
- return await this.submitPsbt(signedPsbt);
748
- }
749
- }
750
-
751
-
752
- //////////////////////////////
753
- //// Execution
754
-
755
- /**
756
- * Executes the swap with the provided bitcoin wallet,
757
- *
758
- * @param dstSigner Signer on the destination network, needs to have the same address as the one specified when
759
- * quote was created, this is required for legacy swaps because the destination wallet needs to actively open
760
- * a bitcoin swap address to which the BTC is then sent, this means that the address also needs to have enough
761
- * native tokens to pay for gas on the destination network
762
- * @param wallet Bitcoin wallet to use to sign the bitcoin transaction, can also be null - then the execution waits
763
- * till a transaction is received from an external wallet
764
- * @param callbacks Callbacks to track the progress of the swap
765
- * @param options Optional options for the swap like feeRate, AbortSignal, and timeouts/intervals
766
- *
767
- * @returns {boolean} Whether a swap was settled automatically by swap watchtowers or requires manual claim by the
768
- * user, in case `false` is returned the user should call `swap.claim()` to settle the swap on the destination manually
769
- */
770
- async execute(
771
- dstSigner: T["Signer"] | T["NativeSigner"],
772
- wallet?: IBitcoinWallet | MinimalBitcoinWalletInterfaceWithSigner | null | undefined,
773
- callbacks?: {
774
- onDestinationCommitSent?: (destinationCommitTxId: string) => void,
775
- onSourceTransactionSent?: (sourceTxId: string) => void,
776
- onSourceTransactionConfirmationStatus?: (sourceTxId?: string, confirmations?: number, targetConfirations?: number, etaMs?: number) => void,
777
- onSourceTransactionConfirmed?: (sourceTxId: string) => void,
778
- onSwapSettled?: (destinationTxId: string) => void
779
- },
780
- options?: {
781
- feeRate?: number,
782
- abortSignal?: AbortSignal,
783
- btcTxCheckIntervalSeconds?: number,
784
- maxWaitTillAutomaticSettlementSeconds?: number
785
- }
786
- ): Promise<boolean> {
787
- if(this._state===FromBTCSwapState.FAILED) throw new Error("Swap failed!");
788
- if(this._state===FromBTCSwapState.EXPIRED) throw new Error("Swap address expired!");
789
- if(this._state===FromBTCSwapState.QUOTE_EXPIRED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) throw new Error("Swap quote expired!");
790
- if(this._state===FromBTCSwapState.CLAIM_CLAIMED) throw new Error("Swap already settled!");
791
-
792
- if(this._state===FromBTCSwapState.PR_CREATED) {
793
- await this.commit(dstSigner, options?.abortSignal, undefined, callbacks?.onDestinationCommitSent);
794
- }
795
- if(this._state===FromBTCSwapState.CLAIM_COMMITED) {
796
- if(wallet!=null) {
797
- const bitcoinPaymentSent = await this.getBitcoinPayment();
798
-
799
- if(bitcoinPaymentSent==null && this.txId==null) {
800
- //Send btc tx
801
- const txId = await this.sendBitcoinTransaction(wallet, options?.feeRate);
802
- if(callbacks?.onSourceTransactionSent!=null) callbacks.onSourceTransactionSent(txId);
803
- }
804
- }
805
-
806
- const txId = await this.waitForBitcoinTransaction(callbacks?.onSourceTransactionConfirmationStatus, options?.btcTxCheckIntervalSeconds, options?.abortSignal);
807
- if (callbacks?.onSourceTransactionConfirmed != null) callbacks.onSourceTransactionConfirmed(txId);
808
- }
809
-
810
- // @ts-ignore
811
- if(this._state===FromBTCSwapState.CLAIM_CLAIMED) return true;
812
-
813
- if(this._state===FromBTCSwapState.BTC_TX_CONFIRMED) {
814
- const success = await this.waitTillClaimed(options?.maxWaitTillAutomaticSettlementSeconds ?? 60, options?.abortSignal);
815
- if(success && callbacks?.onSwapSettled!=null) callbacks.onSwapSettled(this.getOutputTxId()!);
816
- return success;
817
- }
818
-
819
- throw new Error("Invalid state reached!");
820
- }
821
-
822
- /**
823
- * @internal
824
- */
825
- protected async _getExecutionStatus(options?: {
826
- maxWaitTillAutomaticSettlementSeconds?: number
827
- }) {
828
- const state = this._state;
829
- const now = Date.now();
830
- const timeoutTime = this.getTimeoutTime();
831
-
832
- let confirmations: {
833
- current: number,
834
- target: number,
835
- etaSeconds: number
836
- } | undefined;
837
- let bitcoinTxId: string | undefined;
838
-
839
- let destinationSetupStatus: SwapExecutionStepSetup<T["ChainId"]>["status"] = "awaiting";
840
- let bitcoinPaymentStatus: SwapExecutionStepPayment<"BITCOIN">["status"] = "inactive";
841
- let destinationSettlementStatus: SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">["status"] = "inactive";
842
- let buildCurrentAction: (actionOptions?: {
843
- bitcoinFeeRate?: number,
844
- bitcoinWallet?: MinimalBitcoinWalletInterface,
845
- skipChecks?: boolean,
846
- manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
847
- }) => Promise<
848
- SwapExecutionActionSendToAddress<false> |
849
- SwapExecutionActionSignPSBT<"FUNDED_PSBT"> |
850
- SwapExecutionActionWait<"BITCOIN_CONFS" | "SETTLEMENT"> |
851
- SwapExecutionActionSignSmartChainTx<T> |
852
- undefined
853
- > = async () => undefined;
854
-
855
- switch(state) {
856
- case FromBTCSwapState.PR_CREATED: {
857
- const quoteValid = await this._verifyQuoteValid();
858
- destinationSetupStatus = quoteValid && timeoutTime>=now ? "awaiting" : "soft_expired";
859
- if(quoteValid && timeoutTime>=now) {
860
- buildCurrentAction = this._buildInitSmartChainTxAction.bind(this);
861
- }
862
- break;
863
- }
864
- case FromBTCSwapState.QUOTE_SOFT_EXPIRED:
865
- destinationSetupStatus = "soft_expired";
866
- break;
867
- case FromBTCSwapState.QUOTE_EXPIRED:
868
- destinationSetupStatus = "expired";
869
- break;
870
- case FromBTCSwapState.CLAIM_COMMITED:
871
- case FromBTCSwapState.EXPIRED:
872
- case FromBTCSwapState.FAILED:
873
- const bitcoinPayment = this.address==null ? null : await this.getBitcoinPayment();
874
- bitcoinTxId = bitcoinPayment?.txId;
875
- let bitcoinConfirmationDelay: number | undefined;
876
- if(bitcoinPayment!=null && bitcoinPayment.confirmations < bitcoinPayment.targetConfirmations) {
877
- const tx = await this.wrapper._btcRpc.getTransaction(bitcoinPayment.txId);
878
- const result = tx==null
879
- ? null
880
- : await this.wrapper._btcRpc.getConfirmationDelay(tx, bitcoinPayment.targetConfirmations);
881
- bitcoinConfirmationDelay = result ?? -1;
882
- }
883
-
884
- destinationSetupStatus = "completed";
885
- if(bitcoinPayment==null) {
886
- if(this.txId!=null) {
887
- bitcoinPaymentStatus = state===FromBTCSwapState.FAILED ? "expired" : "received";
888
- if(state!==FromBTCSwapState.FAILED) {
889
- buildCurrentAction = this._buildWaitBitcoinConfirmationsAction.bind(this, -1, "Wait for bitcoin transaction to be picked up by the RPC and confirmed.");
890
- }
891
- } else {
892
- bitcoinPaymentStatus = "awaiting";
893
- if(state===FromBTCSwapState.EXPIRED) bitcoinPaymentStatus = "soft_expired";
894
- if(state===FromBTCSwapState.FAILED) bitcoinPaymentStatus = "expired";
895
- if(
896
- state===FromBTCSwapState.CLAIM_COMMITED && timeoutTime>=now &&
897
- this.address!=null && this.amount!=null
898
- ) {
899
- buildCurrentAction = this._buildSendToAddressOrSignPsbtAction.bind(this);
900
- }
901
- }
902
- } else if(bitcoinPayment.confirmations >= bitcoinPayment.targetConfirmations) {
903
- bitcoinPaymentStatus = "confirmed";
904
- if(state!==FromBTCSwapState.FAILED) {
905
- buildCurrentAction = this._buildWaitBitcoinConfirmationsAction.bind(this, bitcoinConfirmationDelay ?? -1, undefined);
906
- }
907
- } else {
908
- bitcoinPaymentStatus = "received";
909
- confirmations = {
910
- current: bitcoinPayment.confirmations,
911
- target: bitcoinPayment.targetConfirmations,
912
- etaSeconds: bitcoinConfirmationDelay ?? -1
913
- };
914
- if(state!==FromBTCSwapState.FAILED) {
915
- buildCurrentAction = this._buildWaitBitcoinConfirmationsAction.bind(this, bitcoinConfirmationDelay ?? -1, undefined);
916
- }
917
- }
918
- destinationSettlementStatus = state===FromBTCSwapState.FAILED ? "expired" : "inactive";
919
- break;
920
- case FromBTCSwapState.BTC_TX_CONFIRMED:
921
- destinationSetupStatus = "completed";
922
- bitcoinPaymentStatus = "confirmed";
923
- if(
924
- this.btcTxConfirmedAt==null ||
925
- options?.maxWaitTillAutomaticSettlementSeconds===0 ||
926
- (now - this.btcTxConfirmedAt) > (options?.maxWaitTillAutomaticSettlementSeconds ?? 60)*1000
927
- ) {
928
- destinationSettlementStatus = "awaiting_manual";
929
- buildCurrentAction = this._buildClaimSmartChainTxAction.bind(this);
930
- } else {
931
- destinationSettlementStatus = "awaiting_automatic";
932
- buildCurrentAction = this._buildWaitSettlementAction.bind(this, options?.maxWaitTillAutomaticSettlementSeconds);
933
- }
934
- break;
935
- case FromBTCSwapState.CLAIM_CLAIMED:
936
- destinationSetupStatus = "completed";
937
- bitcoinPaymentStatus = "confirmed";
938
- destinationSettlementStatus = "settled";
939
- break;
940
- }
941
-
942
- if(bitcoinPaymentStatus==="confirmed") {
943
- const requiredConfirmations = this.getRequiredConfirmationsCount();
944
- if(!Number.isNaN(requiredConfirmations)) {
945
- confirmations = {
946
- current: requiredConfirmations,
947
- target: requiredConfirmations,
948
- etaSeconds: 0
949
- };
950
- }
951
- }
952
-
953
- return {
954
- steps: [
955
- {
956
- type: "Setup",
957
- side: "destination",
958
- chain: this.chainIdentifier,
959
- title: "Open Bitcoin swap address",
960
- description: `Create the escrow on the ${this.chainIdentifier} side to open the Bitcoin swap address`,
961
- status: destinationSetupStatus,
962
- setupTxId: this._commitTxId
963
- },
964
- {
965
- type: "Payment",
966
- side: "source",
967
- chain: "BITCOIN",
968
- title: "Bitcoin payment",
969
- description: "Send Bitcoin to the swap address and wait for the transaction to confirm",
970
- status: bitcoinPaymentStatus,
971
- confirmations,
972
- initTxId: this.txId ?? bitcoinTxId,
973
- settleTxId: this.txId
974
- },
975
- {
976
- type: "Settlement",
977
- side: "destination",
978
- chain: this.chainIdentifier,
979
- title: "Destination settlement",
980
- description: `Wait for automatic settlement on the ${this.chainIdentifier} side, or settle manually if it takes too long`,
981
- status: destinationSettlementStatus,
982
- initTxId: this._commitTxId,
983
- settleTxId: this._claimTxId
984
- }
985
- ] as [
986
- SwapExecutionStepSetup<T["ChainId"]>,
987
- SwapExecutionStepPayment<"BITCOIN">,
988
- SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
989
- ],
990
- buildCurrentAction,
991
- state
992
- };
993
- }
994
-
995
- /**
996
- * @inheritDoc
997
- * @internal
998
- */
999
- async _submitExecutionTransactions(txs: (T["SignedTXType"] | Transaction | string)[], abortSignal?: AbortSignal, requiredStates?: FromBTCSwapState[], idempotent?: boolean): Promise<string[]> {
1000
- if(txs.length===0) throw new Error("Need to submit at least 1 transaction in the array, submitted empty array of transactions!");
1001
-
1002
- if(idempotent) {
1003
- // Handle idempotent calls
1004
- let idempotencyTriggered = false;
1005
- const txIds: string[] = [];
1006
- for(let tx of txs) {
1007
- let parsedTx: T["SignedTXType"] | Transaction | undefined;
1008
- if(typeof(tx)==="string") {
1009
- try {
1010
- parsedTx = await this.wrapper._chain.deserializeSignedTx(tx);
1011
- } catch (e) {}
1012
- try {
1013
- parsedTx = parsePsbtTransaction(tx);
1014
- } catch (e) {}
1015
- } else {
1016
- parsedTx = tx;
1017
- }
1018
-
1019
- if(parsedTx==null) {
1020
- this.logger.debug("_submitExecutionTransactions(): Failed to parse provided execution transaction: ", tx);
1021
- continue;
1022
- }
1023
-
1024
- if(parsedTx instanceof Transaction) {
1025
- // Bitcoin tx
1026
- const btcTx = await this.wrapper._btcRpc.parseTransaction(Buffer.from(parsedTx.toBytes(true)).toString("hex"));
1027
- if(btcTx.txid===this.txId) idempotencyTriggered = true;
1028
- txIds.push(btcTx.txid);
1029
- } else {
1030
- // SC tx
1031
- if(this.wrapper._chain.getTxId!=null) {
1032
- const txId = await this.wrapper._chain.getTxId(parsedTx);
1033
- if(this._commitTxId===txId || this._claimTxId===txId) idempotencyTriggered = true;
1034
- txIds.push(txId);
1035
- }
1036
- }
1037
- }
1038
- if(idempotencyTriggered) return txIds;
1039
- }
1040
-
1041
- if(requiredStates!=null && !requiredStates.includes(this._state)) throw new Error("Swap state has changed before transactions were submitted!");
1042
-
1043
- if(this._state===FromBTCSwapState.CLAIM_COMMITED) {
1044
- let psbt: string | Transaction;
1045
- if(txs.length!==1) throw new Error("Need to submit exactly 1 signed PSBT!");
1046
- if(typeof(txs[0])!=="string" && !(txs[0] instanceof Transaction))
1047
- throw new Error("Must submit a valid PSBT as hex/base64 string or `@scure/btc-signer` Transaction object!");
1048
- psbt = txs[0];
1049
- return [await this.submitPsbt(psbt)];
1050
- }
1051
-
1052
- if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
1053
- if(!await this._verifyQuoteValid()) throw new Error("Quote is already expired!");
1054
- if(this.getTimeoutTime()<Date.now()) throw new Error("Swap address already expired or close to expiry!");
1055
-
1056
- const parsedTxs: T["SignedTXType"][] = [];
1057
- for(let tx of txs) {
1058
- parsedTxs.push(typeof(tx)==="string" ? await this.wrapper._chain.deserializeSignedTx(tx) : tx);
1059
- }
1060
- const txIds = await this.wrapper._chain.sendSignedAndConfirm(parsedTxs, true, abortSignal, false);
1061
- await this.waitTillCommited(abortSignal);
1062
- return txIds;
1063
- }
1064
-
1065
- if(this._state===FromBTCSwapState.BTC_TX_CONFIRMED) {
1066
- const parsedTxs: T["SignedTXType"][] = [];
1067
- for(let tx of txs) {
1068
- parsedTxs.push(typeof(tx)==="string" ? await this.wrapper._chain.deserializeSignedTx(tx) : tx);
1069
- }
1070
- const txIds = await this.wrapper._chain.sendSignedAndConfirm(parsedTxs, true, abortSignal, false);
1071
- await this.waitTillClaimed(undefined, abortSignal);
1072
- return txIds;
1073
- }
1074
-
1075
- throw new Error("Invalid swap state for transaction submission!");
1076
- }
1077
-
1078
- /**
1079
- * @internal
1080
- */
1081
- private async _buildSendToAddressOrSignPsbtAction(actionOptions?: {
1082
- bitcoinFeeRate?: number,
1083
- bitcoinWallet?: MinimalBitcoinWalletInterface,
1084
- }): Promise<
1085
- SwapExecutionActionSendToAddress<false> |
1086
- SwapExecutionActionSignPSBT<"FUNDED_PSBT">
1087
- > {
1088
- if(this.address==null) throw new Error("Bitcoin swap address not known!");
1089
- if(this.amount==null) throw new Error("Bitcoin swap amount not known!");
1090
-
1091
- if(actionOptions?.bitcoinWallet==null) {
1092
- return {
1093
- type: "SendToAddress",
1094
- name: "Deposit on Bitcoin",
1095
- description: "Send funds to the bitcoin swap address",
1096
- chain: "BITCOIN",
1097
- txs: [{
1098
- type: "BITCOIN_ADDRESS",
1099
- address: this.address,
1100
- hyperlink: this._getHyperlink(),
1101
- amount: toTokenAmount(this.amount, BitcoinTokens.BTC, this.wrapper._prices)
1102
- }],
1103
- waitForTransactions: async (
1104
- maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal
1105
- ) => {
1106
- let btcTxId: string | undefined;
1107
- const abortController = extendAbortController(
1108
- abortSignal, maxWaitTimeSeconds, "Timed out waiting for bitcoin transaction"
1109
- );
1110
-
1111
- try {
1112
- return await this.waitForBitcoinTransaction(
1113
- (txId) => {
1114
- btcTxId = txId;
1115
- abortController.abort();
1116
- },
1117
- pollIntervalSeconds,
1118
- abortController.signal
1119
- );
1120
- } catch (e) {
1121
- if(btcTxId!=null) return btcTxId;
1122
- throw e;
1123
- }
1124
- }
1125
- } as SwapExecutionActionSendToAddress<false>;
1126
- }
1127
-
1128
- return {
1129
- type: "SignPSBT",
1130
- name: "Deposit on Bitcoin",
1131
- description: "Send funds to the bitcoin swap address",
1132
- chain: "BITCOIN",
1133
- txs: [{
1134
- ...await this.getFundedPsbt(actionOptions.bitcoinWallet, actionOptions?.bitcoinFeeRate),
1135
- type: "FUNDED_PSBT"
1136
- }],
1137
- submitPsbt: async (signedPsbt: string | Transaction | (string | Transaction)[], idempotent?: boolean) => {
1138
- return this._submitExecutionTransactions(
1139
- Array.isArray(signedPsbt) ? signedPsbt : [signedPsbt],
1140
- undefined,
1141
- [FromBTCSwapState.CLAIM_COMMITED],
1142
- idempotent
1143
- );
1144
- }
1145
- } as SwapExecutionActionSignPSBT<"FUNDED_PSBT">;
1146
- }
1147
-
1148
- /**
1149
- * @internal
1150
- */
1151
- private async _buildWaitBitcoinConfirmationsAction(confirmationDelay: number, description?: string): Promise<SwapExecutionActionWait<"BITCOIN_CONFS">> {
1152
- return {
1153
- type: "Wait",
1154
- name: "Bitcoin confirmations",
1155
- description: description ?? "Wait for bitcoin transaction to confirm",
1156
- pollTimeSeconds: 10,
1157
- expectedTimeSeconds: confirmationDelay===-1 ? -1 : Math.floor(confirmationDelay/1000),
1158
- wait: async (
1159
- maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal,
1160
- btcConfirmationsCallback?: (txId?: string, confirmations?: number, targetConfirmations?: number, txEtaMs?: number) => void
1161
- ) => {
1162
- const abortController = extendAbortController(
1163
- abortSignal, maxWaitTimeSeconds, "Timed out waiting for bitcoin transaction to confirm"
1164
- );
1165
- await this.waitForBitcoinTransaction(btcConfirmationsCallback, pollIntervalSeconds, abortController.signal);
1166
- }
1167
- } as SwapExecutionActionWait<"BITCOIN_CONFS">;
1168
- }
1169
-
1170
- /**
1171
- * @internal
1172
- */
1173
- private async _buildWaitSettlementAction(maxWaitTillAutomaticSettlementSeconds?: number): Promise<SwapExecutionActionWait<"SETTLEMENT">> {
1174
- return {
1175
- type: "Wait",
1176
- name: "Automatic settlement",
1177
- description: "Wait for automatic settlement by the watchtower",
1178
- pollTimeSeconds: 5,
1179
- expectedTimeSeconds: 10,
1180
- wait: async (
1181
- maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal
1182
- ) => {
1183
- await this.waitTillClaimed(maxWaitTimeSeconds ?? maxWaitTillAutomaticSettlementSeconds ?? 60, abortSignal, pollIntervalSeconds);
1184
- }
1185
- } as SwapExecutionActionWait<"SETTLEMENT">;
1186
- }
1187
-
1188
- /**
1189
- * @internal
1190
- */
1191
- private async _buildInitSmartChainTxAction(actionOptions?: {
1192
- skipChecks?: boolean,
1193
- }): Promise<SwapExecutionActionSignSmartChainTx<T>> {
1194
- return {
1195
- type: "SignSmartChainTransaction",
1196
- name: "Initiate swap",
1197
- description: `Opens up the bitcoin swap address on the ${this.chainIdentifier} side`,
1198
- chain: this.chainIdentifier,
1199
- txs: await this.prepareTransactions(this.txsCommit(actionOptions?.skipChecks)),
1200
- submitTransactions: async (txs: (T["SignedTXType"] | string)[], abortSignal?: AbortSignal, idempotent?: boolean) => {
1201
- return this._submitExecutionTransactions(
1202
- txs,
1203
- abortSignal,
1204
- [FromBTCSwapState.PR_CREATED, FromBTCSwapState.QUOTE_SOFT_EXPIRED],
1205
- idempotent
1206
- );
1207
- },
1208
- requiredSigner: this._getInitiator()
1209
- } as SwapExecutionActionSignSmartChainTx<T>;
1210
- }
1211
-
1212
- /**
1213
- * @inheritDoc
1214
- * @internal
1215
- */
1216
- private async _buildClaimSmartChainTxAction(actionOptions?: {
1217
- manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
1218
- }): Promise<SwapExecutionActionSignSmartChainTx<T>> {
1219
- const signerAddress =
1220
- await this.wrapper._getSignerAddress(actionOptions?.manualSettlementSmartChainSigner);
1221
-
1222
- return {
1223
- type: "SignSmartChainTransaction",
1224
- name: "Settle manually",
1225
- description: "Manually settle (claim) the swap on the destination smart chain",
1226
- chain: this.chainIdentifier,
1227
- txs: await this.prepareTransactions(this.txsClaim(actionOptions?.manualSettlementSmartChainSigner)),
1228
- submitTransactions: async (txs: (T["SignedTXType"] | string)[], abortSignal?: AbortSignal, idempotent?: boolean) => {
1229
- return this._submitExecutionTransactions(
1230
- txs,
1231
- abortSignal,
1232
- [FromBTCSwapState.BTC_TX_CONFIRMED],
1233
- idempotent
1234
- );
1235
- },
1236
- requiredSigner: signerAddress ?? this._getInitiator()
1237
- } as SwapExecutionActionSignSmartChainTx<T>;
1238
- }
1239
-
1240
- /**
1241
- * @inheritDoc
1242
- *
1243
- * @param options.bitcoinFeeRate Optional fee rate to use for the created Bitcoin transaction
1244
- * @param options.bitcoinWallet Bitcoin wallet to use, when provided the function returns a funded
1245
- * psbt (`"FUNDED_PSBT"`), if not passed just a bitcoin receive address is returned (`"ADDRESS"`)
1246
- * @param options.skipChecks Skip checks like making sure init signature is still valid and swap
1247
- * wasn't commited yet (this is handled on swap creation, if you commit right after quoting, you
1248
- * can use `skipChecks=true`)
1249
- * @param options.manualSettlementSmartChainSigner Optional smart chain signer to create a manual claim (settlement) transaction
1250
- * @param options.maxWaitTillAutomaticSettlementSeconds Maximum time to wait for an automatic settlement after
1251
- * the bitcoin transaction is confirmed (defaults to 60 seconds)
1252
- */
1253
- async getExecutionAction(options?: {
1254
- bitcoinFeeRate?: number,
1255
- bitcoinWallet?: MinimalBitcoinWalletInterface,
1256
- skipChecks?: boolean,
1257
- manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
1258
- maxWaitTillAutomaticSettlementSeconds?: number
1259
- }): Promise<
1260
- SwapExecutionActionSendToAddress<false> |
1261
- SwapExecutionActionSignPSBT<"FUNDED_PSBT"> |
1262
- SwapExecutionActionWait<"BITCOIN_CONFS" | "SETTLEMENT"> |
1263
- SwapExecutionActionSignSmartChainTx<T> |
1264
- undefined
1265
- > {
1266
- const executionStatus = await this._getExecutionStatus(options);
1267
- return executionStatus.buildCurrentAction(options);
1268
- }
1269
-
1270
- /**
1271
- * @inheritDoc
1272
- */
1273
- async getExecutionStatus(options?: {
1274
- skipBuildingAction?: boolean,
1275
- bitcoinFeeRate?: number,
1276
- bitcoinWallet?: MinimalBitcoinWalletInterface,
1277
- skipChecks?: boolean,
1278
- manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
1279
- maxWaitTillAutomaticSettlementSeconds?: number
1280
- }): Promise<{
1281
- steps: [
1282
- SwapExecutionStepSetup<T["ChainId"]>,
1283
- SwapExecutionStepPayment<"BITCOIN">,
1284
- SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
1285
- ],
1286
- currentAction:
1287
- SwapExecutionActionSendToAddress<false> |
1288
- SwapExecutionActionSignPSBT<"FUNDED_PSBT"> |
1289
- SwapExecutionActionWait<"BITCOIN_CONFS" | "SETTLEMENT"> |
1290
- SwapExecutionActionSignSmartChainTx<T> |
1291
- undefined,
1292
- stateInfo: SwapStateInfo<FromBTCSwapState>
1293
- }> {
1294
- const executionStatus = await this._getExecutionStatus(options);
1295
- return {
1296
- steps: executionStatus.steps,
1297
- currentAction: options?.skipBuildingAction ? undefined : await executionStatus.buildCurrentAction(options),
1298
- stateInfo: this._getStateInfo(executionStatus.state)
1299
- };
1300
- }
1301
-
1302
- /**
1303
- * @inheritDoc
1304
- */
1305
- async getExecutionSteps(options?: {
1306
- maxWaitTillAutomaticSettlementSeconds?: number
1307
- }): Promise<[
1308
- SwapExecutionStepSetup<T["ChainId"]>,
1309
- SwapExecutionStepPayment<"BITCOIN">,
1310
- SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
1311
- ]> {
1312
- return (await this._getExecutionStatus(options)).steps;
1313
- }
1314
-
1315
- //////////////////////////////
1316
- //// Commit
1317
-
1318
- /**
1319
- * @inheritDoc
1320
- *
1321
- * @throws {Error} If invalid signer is provided that doesn't match the swap data
1322
- */
1323
- async commit(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, skipChecks?: boolean, onBeforeTxSent?: (txId: string) => void): Promise<string> {
1324
- const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
1325
- this.checkSigner(signer);
1326
- let txCount = 0;
1327
- const txs = await this.txsCommit(skipChecks);
1328
- const result = await this.wrapper._chain.sendAndConfirm(
1329
- signer, txs, true, abortSignal, undefined, (txId: string) => {
1330
- txCount++;
1331
- if(onBeforeTxSent!=null && txCount===txs.length) onBeforeTxSent(txId);
1332
- return Promise.resolve();
1333
- }
1334
- );
1335
-
1336
- this._commitTxId = result[result.length - 1];
1337
- if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED || this._state===FromBTCSwapState.QUOTE_EXPIRED) {
1338
- await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
1339
- }
1340
- return this._commitTxId;
1341
- }
1342
-
1343
- /**
1344
- * @inheritDoc
1345
- */
1346
- async waitTillCommited(abortSignal?: AbortSignal): Promise<void> {
1347
- if(this._state===FromBTCSwapState.CLAIM_COMMITED || this._state===FromBTCSwapState.CLAIM_CLAIMED) return Promise.resolve();
1348
- if(this._state!==FromBTCSwapState.PR_CREATED && this._state!==FromBTCSwapState.QUOTE_SOFT_EXPIRED) throw new Error("Invalid state");
1349
-
1350
- const abortController = extendAbortController(abortSignal);
1351
- const result = await Promise.race([
1352
- this.watchdogWaitTillCommited(undefined, abortController.signal),
1353
- this.waitTillState(FromBTCSwapState.CLAIM_COMMITED, "gte", abortController.signal).then(() => 0)
1354
- ]);
1355
- abortController.abort();
1356
-
1357
- if(result===0) {
1358
- this.logger.debug("waitTillCommited(): Resolved from state changed");
1359
- } else if(result!=null) {
1360
- this.logger.debug("waitTillCommited(): Resolved from watchdog - commited");
1361
- }
1362
-
1363
- if(result===null) {
1364
- this.logger.debug("waitTillCommited(): Resolved from watchdog - signature expired");
1365
- if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
1366
- await this._saveAndEmit(FromBTCSwapState.QUOTE_EXPIRED);
1367
- }
1368
- return;
1369
- }
1370
-
1371
- if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
1372
- if(typeof(result)==="object" && (result as any).getInitTxId!=null && this._commitTxId==null)
1373
- this._commitTxId = await (result as any).getInitTxId();
1374
- await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
1375
- }
1376
- }
1377
-
1378
-
1379
- //////////////////////////////
1380
- //// Claim
1381
-
1382
- /**
1383
- * Returns transactions for settling (claiming) the swap if the swap requires manual settlement, you can check so
1384
- * with isClaimable. After sending the transaction manually be sure to call the waitTillClaimed function to wait
1385
- * till the claim transaction is observed, processed by the SDK and state of the swap properly updated.
1386
- *
1387
- * @remarks
1388
- * Might also return transactions necessary to sync the bitcoin light client.
1389
- *
1390
- * @param _signer Address of the signer to create the claim transactions for
1391
- *
1392
- * @throws {Error} If the swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
1393
- */
1394
- async txsClaim(_signer?: string | T["Signer"] | T["NativeSigner"]): Promise<T["TX"][]> {
1395
- let signer: string | T["Signer"] | undefined = undefined;
1396
- if(_signer!=null) {
1397
- if (typeof (_signer) === "string") {
1398
- signer = _signer;
1399
- } else if (isAbstractSigner(_signer)) {
1400
- signer = _signer;
1401
- } else {
1402
- signer = await this.wrapper._chain.wrapSigner(_signer);
1403
- }
1404
- }
1405
-
1406
- if(this._state!==FromBTCSwapState.BTC_TX_CONFIRMED) throw new Error("Must be in BTC_TX_CONFIRMED state!");
1407
- if(this.txId==null || this.vout==null) throw new Error("Bitcoin transaction ID not known!");
1408
-
1409
- const tx = await this.wrapper._btcRpc.getTransaction(this.txId);
1410
- if(tx==null) throw new Error("Bitcoin transaction not found on the network!");
1411
-
1412
- this.requiredConfirmations ??= this.inferRequiredConfirmationsCount(tx, this.vout);
1413
- if(this.requiredConfirmations==null)
1414
- 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.");
1415
-
1416
- if(tx.blockhash==null || tx.confirmations==null || tx.blockheight==null || tx.confirmations<this.requiredConfirmations)
1417
- throw new Error("Bitcoin transaction not confirmed yet!");
1418
-
1419
- return await this._contract.txsClaimWithTxData(signer ?? this._getInitiator(), this._data, {
1420
- blockhash: tx.blockhash,
1421
- confirmations: tx.confirmations,
1422
- txid: tx.txid,
1423
- hex: tx.hex,
1424
- height: tx.blockheight
1425
- }, this.requiredConfirmations, this.vout, undefined, this.wrapper._synchronizer(this._contractVersion), true);
1426
- }
1427
-
1428
- /**
1429
- * Settles the swap by claiming the funds on the destination chain if the swap requires manual settlement, you can
1430
- * check so with isClaimable.
1431
- *
1432
- * @remarks
1433
- * Might also sync the bitcoin light client during the process.
1434
- *
1435
- * @param _signer Signer to use for signing the settlement transactions, can also be different to the recipient
1436
- * @param abortSignal Abort signal
1437
- * @param onBeforeTxSent Optional callback triggered before the claim transaction is broadcasted
1438
- *
1439
- * @returns Transaction ID of the settlement (claim) transaction on the destination smart chain
1440
- */
1441
- async claim(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, onBeforeTxSent?: (txId: string) => void): Promise<string> {
1442
- const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
1443
- let txIds: string[];
1444
- try {
1445
- let txCount = 0;
1446
- const txs = await this.txsClaim(signer);
1447
- txIds = await this.wrapper._chain.sendAndConfirm(
1448
- signer, txs, true, abortSignal, undefined, (txId: string) => {
1449
- txCount++;
1450
- if(onBeforeTxSent!=null && txCount===txs.length) onBeforeTxSent(txId);
1451
- return Promise.resolve();
1452
- }
1453
- );
1454
- } catch (e) {
1455
- this.logger.info("claim(): Failed to claim ourselves, checking swap claim state...");
1456
- if(this._state===FromBTCSwapState.CLAIM_CLAIMED) {
1457
- this.logger.info("claim(): Transaction state is CLAIM_CLAIMED, swap was successfully claimed by the watchtower");
1458
- return this._claimTxId!;
1459
- }
1460
- const status = await this._contract.getCommitStatus(this._getInitiator(), this._data);
1461
- if(status?.type===SwapCommitStateType.PAID) {
1462
- this.logger.info("claim(): Transaction commit status is PAID, swap was successfully claimed by the watchtower");
1463
- if(this._claimTxId==null) this._claimTxId = await status.getClaimTxId();
1464
- const txId = Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
1465
- await this._setBitcoinTxId(txId);
1466
- await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
1467
- return this._claimTxId;
1468
- }
1469
- throw e;
1470
- }
1471
-
1472
- this._claimTxId = txIds[txIds.length - 1];
1473
- if(
1474
- this._state===FromBTCSwapState.CLAIM_COMMITED || this._state===FromBTCSwapState.BTC_TX_CONFIRMED ||
1475
- this._state===FromBTCSwapState.EXPIRED || this._state===FromBTCSwapState.FAILED
1476
- ) {
1477
- await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
1478
- }
1479
- return txIds[txIds.length - 1];
1480
- }
1481
-
1482
- /**
1483
- * @inheritDoc
1484
- *
1485
- * @throws {Error} If swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
1486
- * @throws {Error} If the LP refunded sooner than we were able to claim
1487
- */
1488
- async waitTillClaimed(maxWaitTimeSeconds?: number, abortSignal?: AbortSignal, pollIntervalSeconds?: number): Promise<boolean> {
1489
- if(this._state===FromBTCSwapState.CLAIM_CLAIMED) return Promise.resolve(true);
1490
- if(this._state!==FromBTCSwapState.BTC_TX_CONFIRMED) throw new Error("Invalid state (not BTC_TX_CONFIRMED)");
1491
-
1492
- const abortController = extendAbortController(abortSignal);
1493
-
1494
- let timedOut: boolean = false;
1495
- if(maxWaitTimeSeconds!=null) {
1496
- const timeout = setTimeout(() => {
1497
- timedOut = true;
1498
- abortController.abort();
1499
- }, maxWaitTimeSeconds * 1000);
1500
- abortController.signal.addEventListener("abort", () => clearTimeout(timeout));
1501
- }
1502
-
1503
- let res: 0 | 1 | SwapCommitState;
1504
- try {
1505
- res = await Promise.race([
1506
- this.watchdogWaitTillResult(pollIntervalSeconds, abortController.signal),
1507
- this.waitTillState(FromBTCSwapState.CLAIM_CLAIMED, "eq", abortController.signal).then(() => 0 as const),
1508
- this.waitTillState(FromBTCSwapState.FAILED, "eq", abortController.signal).then(() => 1 as const),
1509
- ]);
1510
- abortController.abort();
1511
- } catch (e) {
1512
- abortController.abort();
1513
- if(timedOut) return false;
1514
- throw e;
1515
- }
1516
-
1517
- if(res===0) {
1518
- this.logger.debug("waitTillClaimed(): Resolved from state change (CLAIM_CLAIMED)");
1519
- return true;
1520
- }
1521
- if(res===1) {
1522
- this.logger.debug("waitTillClaimed(): Resolved from state change (FAILED)");
1523
- throw new Error("Offerer refunded during claiming");
1524
- }
1525
- this.logger.debug("waitTillClaimed(): Resolved from watchdog");
1526
-
1527
- if(res?.type===SwapCommitStateType.PAID) {
1528
- if((this._state as FromBTCSwapState)!==FromBTCSwapState.CLAIM_CLAIMED) {
1529
- if(this._claimTxId==null) this._claimTxId = await res.getClaimTxId();
1530
- const txId = Buffer.from(await res.getClaimResult(), "hex").reverse().toString("hex");
1531
- await this._setBitcoinTxId(txId);
1532
- await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
1533
- }
1534
- }
1535
- if(res?.type===SwapCommitStateType.NOT_COMMITED || res?.type===SwapCommitStateType.EXPIRED) {
1536
- if(
1537
- (this._state as FromBTCSwapState)!==FromBTCSwapState.CLAIM_CLAIMED &&
1538
- (this._state as FromBTCSwapState)!==FromBTCSwapState.FAILED
1539
- ) {
1540
- if(res.getRefundTxId!=null) this._refundTxId = await res.getRefundTxId();
1541
- await this._saveAndEmit(FromBTCSwapState.FAILED);
1542
- }
1543
- throw new Error("Swap expired while waiting for claim!");
1544
- }
1545
-
1546
- return true;
1547
- }
1548
-
1549
-
1550
- //////////////////////////////
1551
- //// Storage
1552
-
1553
- /**
1554
- * @inheritDoc
1555
- */
1556
- serialize(): any {
1557
- return {
1558
- ...super.serialize(),
1559
- address: this.address,
1560
- amount: this.amount==null ? null: this.amount.toString(10),
1561
- requiredConfirmations: this.requiredConfirmations,
1562
- senderAddress: this.senderAddress,
1563
- txId: this.txId,
1564
- vout: this.vout,
1565
- btcTxConfirmedAt: this.btcTxConfirmedAt
1566
- };
1567
- }
1568
-
1569
-
1570
- //////////////////////////////
1571
- //// Swap ticks & sync
1572
-
1573
- /**
1574
- * Checks the swap's state on-chain and compares it to its internal state, updates/changes it according to on-chain
1575
- * data
1576
- *
1577
- * @private
1578
- */
1579
- private async syncStateFromChain(quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState): Promise<boolean> {
1580
- if(
1581
- this._state===FromBTCSwapState.PR_CREATED ||
1582
- this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
1583
- this._state===FromBTCSwapState.CLAIM_COMMITED ||
1584
- this._state===FromBTCSwapState.BTC_TX_CONFIRMED ||
1585
- this._state===FromBTCSwapState.EXPIRED
1586
- ) {
1587
- let quoteExpired: boolean = false;
1588
- if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
1589
- quoteExpired = quoteDefinitelyExpired ?? await this._verifyQuoteDefinitelyExpired(); //Make sure we check for expiry here, to prevent race conditions
1590
- }
1591
-
1592
- const status = commitStatus ?? await this._contract.getCommitStatus(this._getInitiator(), this._data);
1593
- if(status!=null && await this._forciblySetOnchainState(status)) return true;
1594
-
1595
- if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
1596
- if(quoteExpired) {
1597
- this._state = FromBTCSwapState.QUOTE_EXPIRED;
1598
- return true;
1599
- }
1600
- }
1601
- }
1602
-
1603
- return false;
1604
- }
1605
-
1606
- /**
1607
- * @inheritDoc
1608
- * @internal
1609
- */
1610
- _shouldFetchOnchainState(): boolean {
1611
- return this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
1612
- this._state===FromBTCSwapState.CLAIM_COMMITED || this._state===FromBTCSwapState.BTC_TX_CONFIRMED ||
1613
- this._state===FromBTCSwapState.EXPIRED;
1614
- }
1615
-
1616
- /**
1617
- * @inheritDoc
1618
- * @internal
1619
- */
1620
- _shouldFetchExpiryStatus(): boolean {
1621
- return this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED;
1622
- }
1623
-
1624
- /**
1625
- * @inheritDoc
1626
- * @internal
1627
- */
1628
- async _sync(save?: boolean, quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState): Promise<boolean> {
1629
- const changed = await this.syncStateFromChain(quoteDefinitelyExpired, commitStatus);
1630
- if(changed && save) await this._saveAndEmit();
1631
- return changed;
1632
- }
1633
-
1634
- private btcTxLastChecked?: number;
1635
-
1636
- /**
1637
- * @inheritDoc
1638
- * @internal
1639
- */
1640
- async _forciblySetOnchainState(status: SwapCommitState): Promise<boolean> {
1641
- switch(status.type) {
1642
- case SwapCommitStateType.PAID:
1643
- if(this._commitTxId==null && (status as any).getInitTxId!=null) this._commitTxId = await (status as any).getInitTxId();
1644
- if(this._claimTxId==null) this._claimTxId = await status.getClaimTxId();
1645
- const txId = Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
1646
- await this._setBitcoinTxId(txId);
1647
- this._state = FromBTCSwapState.CLAIM_CLAIMED;
1648
- return true;
1649
- case SwapCommitStateType.NOT_COMMITED:
1650
- let changed: boolean = false;
1651
- if(this._commitTxId==null && (status as any).getInitTxId!=null) {
1652
- this._commitTxId = await (status as any).getInitTxId();
1653
- changed = true;
1654
- }
1655
- if(this._refundTxId==null && status.getRefundTxId) {
1656
- this._refundTxId = await status.getRefundTxId();
1657
- changed = true;
1658
- }
1659
- if(this._refundTxId!=null) {
1660
- this._state = FromBTCSwapState.FAILED;
1661
- changed = true;
1662
- }
1663
- return changed;
1664
- case SwapCommitStateType.EXPIRED:
1665
- if(this._commitTxId==null && (status as any).getInitTxId!=null) this._commitTxId = await (status as any).getInitTxId();
1666
- if(this._refundTxId==null && status.getRefundTxId) this._refundTxId = await status.getRefundTxId();
1667
- this._state = this._refundTxId==null ? FromBTCSwapState.QUOTE_EXPIRED : FromBTCSwapState.FAILED;
1668
- return true;
1669
- case SwapCommitStateType.COMMITED:
1670
- let save: boolean = false;
1671
- if(this._commitTxId==null && (status as any).getInitTxId!=null) {
1672
- this._commitTxId = await (status as any).getInitTxId();
1673
- save = true;
1674
- }
1675
- if(this._state!==FromBTCSwapState.CLAIM_COMMITED && this._state!==FromBTCSwapState.BTC_TX_CONFIRMED && this._state!==FromBTCSwapState.EXPIRED) {
1676
- this._state = FromBTCSwapState.CLAIM_COMMITED;
1677
- save = true;
1678
- }
1679
- if(this.address==null) return save;
1680
-
1681
- this.btcTxLastChecked = Date.now();
1682
- const res = await this.getBitcoinPayment();
1683
- if(res!=null) {
1684
- if(this.txId!==res.txId || this.vout!==res.vout || (res.inputAddresses!=null && this.senderAddress==null)) {
1685
- if(res.inputAddresses!=null) this.senderAddress = res.inputAddresses[0];
1686
- this.txId = res.txId;
1687
- this.vout = res.vout;
1688
- save = true;
1689
- }
1690
- if(this.requiredConfirmations!=null && res.confirmations>=this.requiredConfirmations) {
1691
- this.btcTxConfirmedAt ??= Date.now();
1692
- this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
1693
- save = true;
1694
- }
1695
- }
1696
- return save;
1697
- }
1698
- return false;
1699
- }
1700
-
1701
- /**
1702
- * @inheritDoc
1703
- * @internal
1704
- */
1705
- async _tick(save?: boolean): Promise<boolean> {
1706
- switch(this._state) {
1707
- case FromBTCSwapState.PR_CREATED:
1708
- if(this.expiry<Date.now()) {
1709
- this._state = FromBTCSwapState.QUOTE_SOFT_EXPIRED;
1710
- if(save) await this._saveAndEmit();
1711
- return true;
1712
- }
1713
- break;
1714
- case FromBTCSwapState.CLAIM_COMMITED:
1715
- if(this.getTimeoutTime()<Date.now()) {
1716
- this._state = FromBTCSwapState.EXPIRED;
1717
- if(save) await this._saveAndEmit();
1718
- return true;
1719
- }
1720
- case FromBTCSwapState.EXPIRED:
1721
- //Check if bitcoin payment was received at least every 2 minutes
1722
- if(this.btcTxLastChecked==null || Date.now() - this.btcTxLastChecked > 120_000) {
1723
- if(this.address!=null) try {
1724
- this.btcTxLastChecked = Date.now();
1725
- const res = await this.getBitcoinPayment();
1726
- if(res!=null) {
1727
- let shouldSave: boolean = false;
1728
- if(this.txId!==res.txId || this.vout!==res.vout || (res.inputAddresses!=null && this.senderAddress==null)) {
1729
- this.txId = res.txId;
1730
- this.vout = res.vout;
1731
- if(res.inputAddresses!=null) this.senderAddress = res.inputAddresses[0];
1732
- shouldSave = true;
1733
- }
1734
- if(this.requiredConfirmations!=null && res.confirmations>=this.requiredConfirmations) {
1735
- this.btcTxConfirmedAt ??= Date.now();
1736
- this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
1737
- if(save) await this._saveAndEmit();
1738
- shouldSave = true;
1739
- }
1740
- if(shouldSave && save) await this._saveAndEmit();
1741
- return shouldSave;
1742
- }
1743
- } catch (e) {
1744
- this.logger.warn("tickSwap("+this.getIdentifierHashString()+"): ", e);
1745
- }
1746
- }
1747
- break;
1748
- }
1749
-
1750
- return false;
1751
- }
1752
-
1753
- }
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, getSenderAddress, getVoutIndex,
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 {timeoutPromise} from "../../../../utils/TimeoutUtils";
37
+ import {toBitcoinWallet} from "../../../../utils/BitcoinWalletUtils";
38
+ import {
39
+ SwapExecutionActionSendToAddress,
40
+ SwapExecutionActionSignPSBT,
41
+ SwapExecutionActionSignSmartChainTx,
42
+ SwapExecutionActionWait
43
+ } from "../../../../types/SwapExecutionAction";
44
+ import {
45
+ SwapExecutionStepPayment,
46
+ SwapExecutionStepSettlement,
47
+ SwapExecutionStepSetup
48
+ } from "../../../../types/SwapExecutionStep";
49
+ import {SwapStateInfo} from "../../../../types/SwapStateInfo";
50
+
51
+ /**
52
+ * State enum for legacy escrow based Bitcoin -> Smart chain swaps.
53
+ *
54
+ * @category Swaps/Legacy/Bitcoin → Smart chain
55
+ */
56
+ export enum FromBTCSwapState {
57
+ /**
58
+ * Bitcoin swap address has expired and the intermediary (LP) has already refunded
59
+ * its funds. No BTC should be sent anymore!
60
+ */
61
+ FAILED = -4,
62
+ /**
63
+ * Bitcoin swap address has expired, user should not send any BTC anymore! Though
64
+ * the intermediary (LP) hasn't refunded yet. So if there is a transaction already
65
+ * in-flight the swap might still succeed.
66
+ */
67
+ EXPIRED = -3,
68
+ /**
69
+ * Swap has expired for good and there is no way how it can be executed anymore
70
+ */
71
+ QUOTE_EXPIRED = -2,
72
+ /**
73
+ * A swap is almost expired, and it should be presented to the user as expired, though
74
+ * there is still a chance that it will be processed
75
+ */
76
+ QUOTE_SOFT_EXPIRED = -1,
77
+ /**
78
+ * Swap quote was created, use the {@link FromBTCSwap.commit} or {@link FromBTCSwap.txsCommit} functions
79
+ * to initiate it by creating the swap escrow on the destination smart chain
80
+ */
81
+ PR_CREATED = 0,
82
+ /**
83
+ * Swap escrow was initiated (committed) on the destination chain, user can send the BTC to the
84
+ * swap address with the {@link FromBTCSwap.getFundedPsbt}, {@link FromBTCSwap.getAddress} or
85
+ * {@link FromBTCSwap.getHyperlink} functions.
86
+ */
87
+ CLAIM_COMMITED = 1,
88
+ /**
89
+ * Input bitcoin transaction was confirmed, wait for automatic settlement by the watchtowers
90
+ * using the {@link FromBTCSwap.waitTillClaimed} function or settle manually using the {@link FromBTCSwap.claim}
91
+ * or {@link FromBTCSwap.txsClaim} function.
92
+ */
93
+ BTC_TX_CONFIRMED = 2,
94
+ /**
95
+ * Swap successfully settled and funds received on the destination chain
96
+ */
97
+ CLAIM_CLAIMED = 3
98
+ }
99
+
100
+ const FromBTCSwapStateDescription = {
101
+ [FromBTCSwapState.FAILED]: "Bitcoin swap address has expired and the intermediary (LP) has already refunded its funds. No BTC should be sent anymore!",
102
+ [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.",
103
+ [FromBTCSwapState.QUOTE_EXPIRED]: "Swap has expired for good and there is no way how it can be executed anymore",
104
+ [FromBTCSwapState.QUOTE_SOFT_EXPIRED]: "The swap is expired, though there is still a chance that it will be processed",
105
+ [FromBTCSwapState.PR_CREATED]: "Swap quote was created, initiate it by creating the swap escrow on the destination smart chain",
106
+ [FromBTCSwapState.CLAIM_COMMITED]: "Swap escrow was initiated (committed) on the destination chain, user can send the BTC to the Bitcoin swap address.",
107
+ [FromBTCSwapState.BTC_TX_CONFIRMED]: "Input bitcoin transaction was confirmed, wait for automatic settlement by the watchtower or settle manually.",
108
+ [FromBTCSwapState.CLAIM_CLAIMED]: "Swap successfully settled and funds received on the destination chain"
109
+ };
110
+
111
+ export type FromBTCSwapInit<T extends SwapData> = IEscrowSelfInitSwapInit<T> & {
112
+ data: T;
113
+ address?: string;
114
+ amount?: bigint;
115
+ requiredConfirmations?: number;
116
+ };
117
+
118
+ export function isFromBTCSwapInit<T extends SwapData>(obj: any): obj is FromBTCSwapInit<T> {
119
+ return typeof(obj.data) === "object" &&
120
+ (obj.address==null || typeof(obj.address) === "string") &&
121
+ (obj.amount==null || typeof(obj.amount) === "bigint") &&
122
+ (obj.requiredConfirmations==null || typeof(obj.requiredConfirmations) === "number") &&
123
+ isIEscrowSelfInitSwapInit<T>(obj);
124
+ }
125
+
126
+ /**
127
+ * Legacy escrow (PrTLC) based swap for Bitcoin -> Smart chains, requires manual initiation
128
+ * of the swap escrow on the destination chain.
129
+ *
130
+ * @category Swaps/Legacy/Bitcoin → Smart chain
131
+ */
132
+ export class FromBTCSwap<T extends ChainType = ChainType>
133
+ extends IFromBTCSelfInitSwap<T, FromBTCDefinition<T>, FromBTCSwapState>
134
+ implements IBTCWalletSwap, IClaimableSwap<T, FromBTCDefinition<T>, FromBTCSwapState>, IAddressSwap {
135
+
136
+ protected readonly TYPE: SwapType.FROM_BTC = SwapType.FROM_BTC;
137
+ /**
138
+ * @internal
139
+ */
140
+ protected readonly swapStateName = (state: number) => FromBTCSwapState[state];
141
+ /**
142
+ * @internal
143
+ */
144
+ protected readonly swapStateDescription = FromBTCSwapStateDescription;
145
+ /**
146
+ * @internal
147
+ */
148
+ protected readonly logger: LoggerType;
149
+ /**
150
+ * @internal
151
+ */
152
+ protected readonly inputToken: BtcToken<false> = BitcoinTokens.BTC;
153
+ /**
154
+ * @internal
155
+ */
156
+ protected readonly feeRate!: string;
157
+
158
+ /**
159
+ * @internal
160
+ */
161
+ readonly _data!: T["Data"];
162
+
163
+ private address?: string;
164
+ private amount?: bigint;
165
+ private requiredConfirmations?: number;
166
+
167
+ private senderAddress?: string;
168
+ private txId?: string;
169
+ private vout?: number;
170
+
171
+ private btcTxConfirmedAt?: number;
172
+
173
+ constructor(wrapper: FromBTCWrapper<T>, init: FromBTCSwapInit<T["Data"]>);
174
+ constructor(wrapper: FromBTCWrapper<T>, obj: any);
175
+ constructor(wrapper: FromBTCWrapper<T>, initOrObject: FromBTCSwapInit<T["Data"]> | any) {
176
+ if(isFromBTCSwapInit(initOrObject) && initOrObject.url!=null) initOrObject.url += "/frombtc";
177
+ super(wrapper, initOrObject);
178
+ if(isFromBTCSwapInit(initOrObject)) {
179
+ this._state = FromBTCSwapState.PR_CREATED;
180
+ this._data = initOrObject.data;
181
+ this.feeRate = initOrObject.feeRate;
182
+ this.address = initOrObject.address;
183
+ this.amount = initOrObject.amount;
184
+ this.requiredConfirmations = initOrObject.requiredConfirmations;
185
+ } else {
186
+ this.address = initOrObject.address;
187
+ this.amount = toBigInt(initOrObject.amount);
188
+ this.senderAddress = initOrObject.senderAddress;
189
+ this.txId = initOrObject.txId;
190
+ this.vout = initOrObject.vout;
191
+ this.requiredConfirmations = initOrObject.requiredConfirmations ?? this._data.getConfirmationsHint();
192
+ this.btcTxConfirmedAt = initOrObject.btcTxConfirmedAt;
193
+ }
194
+ this.tryRecomputeSwapPrice();
195
+ this.logger = getLogger("FromBTC("+this.getIdentifierHashString()+"): ");
196
+ }
197
+
198
+ /**
199
+ * @inheritDoc
200
+ * @internal
201
+ */
202
+ protected getSwapData(): T["Data"] {
203
+ return this._data;
204
+ }
205
+
206
+ /**
207
+ * @inheritDoc
208
+ * @internal
209
+ */
210
+ protected upgradeVersion() {
211
+ if(this.version == null) {
212
+ switch(this._state) {
213
+ case -2:
214
+ this._state = FromBTCSwapState.FAILED
215
+ break;
216
+ case -1:
217
+ this._state = FromBTCSwapState.QUOTE_EXPIRED
218
+ break;
219
+ case 0:
220
+ this._state = FromBTCSwapState.PR_CREATED
221
+ break;
222
+ case 1:
223
+ this._state = FromBTCSwapState.CLAIM_COMMITED
224
+ break;
225
+ case 2:
226
+ this._state = FromBTCSwapState.BTC_TX_CONFIRMED
227
+ break;
228
+ case 3:
229
+ this._state = FromBTCSwapState.CLAIM_CLAIMED
230
+ break;
231
+ }
232
+ this.version = 1;
233
+ }
234
+ }
235
+
236
+
237
+ //////////////////////////////
238
+ //// Getters & utils
239
+
240
+ /**
241
+ * Returns bitcoin address where the on-chain BTC should be sent to
242
+ */
243
+ getAddress(): string {
244
+ if(this._state===FromBTCSwapState.PR_CREATED) throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
245
+ return this.address ?? "";
246
+ }
247
+
248
+ /**
249
+ * Unsafe bitcoin hyperlink getter, returns the address even before the swap is committed!
250
+ *
251
+ * @private
252
+ */
253
+ private _getHyperlink(): string {
254
+ return this.address==null || this.amount==null ? "" : "bitcoin:"+this.address+"?amount="+encodeURIComponent((Number(this.amount) / 100000000).toString(10));
255
+ }
256
+
257
+ /**
258
+ * @inheritDoc
259
+ */
260
+ getHyperlink(): string {
261
+ if(this._state===FromBTCSwapState.PR_CREATED) throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
262
+ return this._getHyperlink();
263
+ }
264
+
265
+ /**
266
+ * @inheritDoc
267
+ */
268
+ getInputAddress(): string | null {
269
+ return this.senderAddress ?? null;
270
+ }
271
+
272
+ /**
273
+ * @inheritDoc
274
+ */
275
+ getInputTxId(): string | null {
276
+ return this.txId ?? null;
277
+ }
278
+
279
+ private async _setSubmittedBitcoinTx(txId: string, psbt?: Transaction): Promise<void> {
280
+ let changed = false;
281
+ if(this.txId!==txId) {
282
+ this.txId = txId;
283
+ changed = true;
284
+ }
285
+
286
+ const submittedVout = this.address==null || this.amount==null || psbt==null
287
+ ? undefined
288
+ : getVoutIndex(psbt, this.wrapper._options.bitcoinNetwork, this.address, this.amount);
289
+ if(submittedVout!=null && this.vout!==submittedVout) {
290
+ this.vout = submittedVout;
291
+ changed = true;
292
+ }
293
+
294
+ const submittedSenderAddress = psbt==null
295
+ ? undefined
296
+ : getSenderAddress(psbt, this.wrapper._options.bitcoinNetwork);
297
+ if(submittedSenderAddress!=null && this.senderAddress!==submittedSenderAddress) {
298
+ this.senderAddress = submittedSenderAddress;
299
+ changed = true;
300
+ }
301
+
302
+ if(changed) await this._saveAndEmit();
303
+ }
304
+
305
+ /**
306
+ * Returns timeout time (in UNIX milliseconds) when the on-chain address will expire and no funds should be sent
307
+ * to that address anymore
308
+ */
309
+ getTimeoutTime(): number {
310
+ return Number(this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations ?? 6)) * 1000;
311
+ }
312
+
313
+ /**
314
+ * @inheritDoc
315
+ */
316
+ requiresAction(): boolean {
317
+ return this.isClaimable() || (this._state===FromBTCSwapState.CLAIM_COMMITED && this.getTimeoutTime()>Date.now() && this.txId==null);
318
+ }
319
+
320
+ /**
321
+ * @inheritDoc
322
+ */
323
+ isFinished(): boolean {
324
+ return this._state===FromBTCSwapState.CLAIM_CLAIMED || this._state===FromBTCSwapState.QUOTE_EXPIRED || this._state===FromBTCSwapState.FAILED;
325
+ }
326
+
327
+ /**
328
+ * @inheritDoc
329
+ */
330
+ isClaimable(): boolean {
331
+ return this._state===FromBTCSwapState.BTC_TX_CONFIRMED;
332
+ }
333
+
334
+ /**
335
+ * @inheritDoc
336
+ */
337
+ isSuccessful(): boolean {
338
+ return this._state===FromBTCSwapState.CLAIM_CLAIMED;
339
+ }
340
+
341
+ /**
342
+ * @inheritDoc
343
+ */
344
+ isFailed(): boolean {
345
+ return this._state===FromBTCSwapState.FAILED || this._state===FromBTCSwapState.EXPIRED;
346
+ }
347
+
348
+ /**
349
+ * @inheritDoc
350
+ */
351
+ isInProgress(): boolean {
352
+ return this._state===FromBTCSwapState.CLAIM_COMMITED ||
353
+ this._state===FromBTCSwapState.BTC_TX_CONFIRMED;
354
+ }
355
+
356
+ /**
357
+ * @inheritDoc
358
+ */
359
+ isQuoteExpired(): boolean {
360
+ return this._state===FromBTCSwapState.QUOTE_EXPIRED;
361
+ }
362
+
363
+ /**
364
+ * @inheritDoc
365
+ */
366
+ isQuoteSoftExpired(): boolean {
367
+ return this._state===FromBTCSwapState.QUOTE_EXPIRED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED;
368
+ }
369
+
370
+ /**
371
+ * @inheritDoc
372
+ * @internal
373
+ */
374
+ protected canCommit(skipQuoteExpiryChecks?: boolean): boolean {
375
+ if(this._state!==FromBTCSwapState.PR_CREATED && (!skipQuoteExpiryChecks || this._state!==FromBTCSwapState.QUOTE_SOFT_EXPIRED)) return false;
376
+ if(this.requiredConfirmations==null) return false;
377
+ const expiry = this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations);
378
+ const currentTimestamp = BigInt(Math.floor(Date.now()/1000));
379
+
380
+ return (expiry - currentTimestamp) >= this.wrapper._options.minSendWindow;
381
+ }
382
+
383
+
384
+ //////////////////////////////
385
+ //// Amounts & fees
386
+
387
+ /**
388
+ * @inheritDoc
389
+ */
390
+ getInputToken(): BtcToken<false> {
391
+ return BitcoinTokens.BTC;
392
+ }
393
+
394
+ /**
395
+ * @inheritDoc
396
+ */
397
+ getInput(): TokenAmount<BtcToken<false>> {
398
+ return toTokenAmount(this.amount ?? null, this.inputToken, this.wrapper._prices);
399
+ }
400
+
401
+ /**
402
+ * Returns claimer bounty, acting as a reward for watchtowers to claim the swap automatically,
403
+ * this amount is pre-funded by the user on the destination chain when the swap escrow
404
+ * is initiated. For total pre-funded deposit amount see {@link getTotalDeposit}.
405
+ */
406
+ getClaimerBounty(): TokenAmount<SCToken<T["ChainId"]>, true> {
407
+ return toTokenAmount(this._data.getClaimerBounty(), this.wrapper._tokens[this._data.getDepositToken()], this.wrapper._prices);
408
+ }
409
+
410
+
411
+ //////////////////////////////
412
+ //// Bitcoin tx
413
+
414
+ /**
415
+ * If the required number of confirmations is not known, this function tries to infer it by looping through
416
+ * possible confirmation targets and comparing the claim hashes
417
+ *
418
+ * @param btcTx Bitcoin transaction
419
+ * @param vout Output index of the desired output in the bitcoin transaction
420
+ *
421
+ * @private
422
+ */
423
+ private inferRequiredConfirmationsCount(btcTx: Omit<BtcTxWithBlockheight, "hex" | "raw">, vout: number): number | undefined {
424
+ const txOut = btcTx.outs[vout];
425
+ for(let i=1;i<=20;i++) {
426
+ const computedClaimHash = this._contract.getHashForOnchain(
427
+ Buffer.from(txOut.scriptPubKey.hex, "hex"),
428
+ BigInt(txOut.value),
429
+ i
430
+ );
431
+ if(computedClaimHash.toString("hex")===this._data.getClaimHash()) {
432
+ return i;
433
+ }
434
+ }
435
+ }
436
+
437
+ /**
438
+ * @inheritDoc
439
+ */
440
+ getRequiredConfirmationsCount(): number {
441
+ return this.requiredConfirmations ?? NaN;
442
+ }
443
+
444
+ /**
445
+ * Checks whether a bitcoin payment was already made, returns the payment or `null` when no payment has been made.
446
+ *
447
+ * @internal
448
+ */
449
+ protected async getBitcoinPayment(): Promise<{
450
+ txId: string,
451
+ vout: number,
452
+ confirmations: number,
453
+ targetConfirmations: number,
454
+ inputAddresses?: string[]
455
+ } | null> {
456
+ const txoHashHint = this._data.getTxoHashHint();
457
+ if(txoHashHint==null) throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
458
+ 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.");
459
+
460
+ const result = await this.wrapper._btcRpc.checkAddressTxos(this.address, Buffer.from(txoHashHint, "hex"));
461
+ if(result==null) return null;
462
+
463
+ if(this.requiredConfirmations==null) {
464
+ this.requiredConfirmations = this.inferRequiredConfirmationsCount(result.tx, result.vout);
465
+ }
466
+
467
+ return {
468
+ inputAddresses: result.tx.inputAddresses,
469
+ txId: result.tx.txid,
470
+ vout: result.vout,
471
+ confirmations: result.tx.confirmations ?? 0,
472
+ targetConfirmations: this.getRequiredConfirmationsCount()
473
+ }
474
+ }
475
+
476
+ /**
477
+ * Used to set the txId of the bitcoin payment from the on-chain events listener
478
+ *
479
+ * @param txId Transaction ID that settled the swap on the smart chain
480
+ *
481
+ * @internal
482
+ */
483
+ async _setBitcoinTxId(txId: string) {
484
+ if(this.txId!==txId || this.address==null || this.vout==null || this.senderAddress==null || this.amount==null) {
485
+ const btcTx = await this.wrapper._btcRpc.getTransaction(txId);
486
+ if(btcTx==null) return;
487
+
488
+ const txoHashHint = this._data.getTxoHashHint();
489
+ if(txoHashHint!=null) {
490
+ const expectedTxoHash = Buffer.from(txoHashHint, "hex");
491
+ const vout = btcTx.outs.findIndex(out => getTxoHash(out.scriptPubKey.hex, out.value).equals(expectedTxoHash));
492
+ if(vout!==-1) {
493
+ this.vout = vout;
494
+ //If amount or address are not known, parse them from the bitcoin tx
495
+ // this can happen if the swap is recovered from on-chain data and
496
+ // hence doesn't contain the address and amount data
497
+ if(this.amount==null) this.amount = BigInt(btcTx.outs[vout].value);
498
+ if(this.address==null) try {
499
+ this.address = fromOutputScript(this.wrapper._options.bitcoinNetwork, btcTx.outs[vout].scriptPubKey.hex);
500
+ } catch (e: any) {
501
+ this.logger.warn("_setBitcoinTxId(): Failed to parse address from output script: ", e);
502
+ }
503
+ if(this.requiredConfirmations==null) {
504
+ this.requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
505
+ }
506
+ }
507
+ }
508
+
509
+ if(btcTx.inputAddresses!=null) {
510
+ this.senderAddress = btcTx.inputAddresses[0];
511
+ }
512
+ }
513
+
514
+ this.txId = txId;
515
+ }
516
+
517
+ /**
518
+ * @inheritDoc
519
+ *
520
+ * @throws {Error} if in invalid state (must be {@link FromBTCSwapState.CLAIM_COMMITED})
521
+ */
522
+ async waitForBitcoinTransaction(
523
+ updateCallback?: (txId?: string, confirmations?: number, targetConfirmations?: number, txEtaMs?: number) => void,
524
+ checkIntervalSeconds?: number,
525
+ abortSignal?: AbortSignal
526
+ ): Promise<string> {
527
+ if(this._state!==FromBTCSwapState.CLAIM_COMMITED && this._state!==FromBTCSwapState.EXPIRED) throw new Error("Must be in COMMITED state!");
528
+ const txoHashHint = this._data.getTxoHashHint();
529
+ if(txoHashHint==null) throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
530
+ 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.");
531
+
532
+ let abortedDueToEnoughConfirmationsResult: {
533
+ tx: Omit<BtcTxWithBlockheight, "hex" | "raw">, vout: number
534
+ } | undefined;
535
+ const abortController = extendAbortController(abortSignal);
536
+
537
+ const result = await this.wrapper._btcRpc.waitForAddressTxo(
538
+ this.address,
539
+ Buffer.from(txoHashHint, "hex"),
540
+ this.requiredConfirmations ?? 6, //In case confirmation count is not known, we use a conservative estimate
541
+ (btcTx?: Omit<BtcTxWithBlockheight, "hex" | "raw">, vout?: number, txEtaMs?: number) => {
542
+ let requiredConfirmations = this.requiredConfirmations;
543
+
544
+ if(btcTx!=null && vout!=null && requiredConfirmations==null) {
545
+ requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
546
+ }
547
+
548
+ if(btcTx!=null && (
549
+ btcTx.txid!==this.txId ||
550
+ this.vout==null ||
551
+ this.senderAddress==null ||
552
+ (this.requiredConfirmations==null && requiredConfirmations!=null)
553
+ )) {
554
+ this.txId = btcTx.txid;
555
+ this.vout = vout;
556
+ this.requiredConfirmations = requiredConfirmations;
557
+ if(btcTx.inputAddresses!=null) this.senderAddress = btcTx.inputAddresses[0];
558
+ this._saveAndEmit().catch(e => {
559
+ this.logger.error("waitForBitcoinTransaction(): Failed to save swap from within waitForAddressTxo callback:", e)
560
+ });
561
+ }
562
+
563
+ //Abort the loop as soon as the transaction gets enough confirmations, this is required in case
564
+ // we pass a default 6 confirmations to the fn, but then are able to infer the actual confirmation
565
+ // target from the prior block
566
+ if(btcTx?.confirmations!=null && requiredConfirmations!=null && requiredConfirmations<=btcTx.confirmations && vout!=null) {
567
+ abortedDueToEnoughConfirmationsResult = {
568
+ tx: btcTx,
569
+ vout
570
+ };
571
+ abortController.abort();
572
+ return;
573
+ }
574
+
575
+ if(updateCallback!=null) updateCallback(btcTx?.txid, btcTx==null ? undefined : (btcTx?.confirmations ?? 0), requiredConfirmations ?? NaN, txEtaMs);
576
+ },
577
+ abortController.signal,
578
+ checkIntervalSeconds
579
+ ).catch(e => {
580
+ //We catch the case when the loop was aborted due to the transaction getting enough confirmations
581
+ if(abortedDueToEnoughConfirmationsResult!=null) return abortedDueToEnoughConfirmationsResult;
582
+ throw e;
583
+ });
584
+
585
+ if(abortSignal!=null) abortSignal.throwIfAborted();
586
+
587
+ this.txId = result.tx.txid;
588
+ this.vout = result.vout;
589
+ if(result.tx.inputAddresses!=null) this.senderAddress = result.tx.inputAddresses[0];
590
+
591
+ if(
592
+ (this._state as FromBTCSwapState)!==FromBTCSwapState.CLAIM_CLAIMED &&
593
+ (this._state as FromBTCSwapState)!==FromBTCSwapState.FAILED
594
+ ) {
595
+ this.btcTxConfirmedAt ??= Date.now();
596
+ this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
597
+ }
598
+
599
+ await this._saveAndEmit();
600
+
601
+ return result.tx.txid;
602
+ }
603
+
604
+ /**
605
+ * Private getter of the funded PSBT that doesn't check current state
606
+ *
607
+ * @param _bitcoinWallet Bitcoin wallet to fund the PSBT with
608
+ * @param feeRate Optional bitcoin fee rate in sats/vB
609
+ * @param additionalOutputs Optional additional outputs that should also be included in the generated PSBT
610
+ *
611
+ * @private
612
+ */
613
+ private async _getFundedPsbt(
614
+ _bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface,
615
+ feeRate?: number,
616
+ additionalOutputs?: ({amount: bigint, outputScript: Uint8Array} | {amount: bigint, address: string})[]
617
+ ): Promise<{psbt: Transaction, psbtHex: string, psbtBase64: string, signInputs: number[], feeRate: number}> {
618
+ 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.");
619
+
620
+ let bitcoinWallet: IBitcoinWallet;
621
+ if(isIBitcoinWallet(_bitcoinWallet)) {
622
+ bitcoinWallet = _bitcoinWallet;
623
+ } else {
624
+ bitcoinWallet = new SingleAddressBitcoinWallet(this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork, _bitcoinWallet);
625
+ }
626
+ //TODO: Maybe re-introduce fee rate check here if passed from the user
627
+ if(feeRate==null) {
628
+ feeRate = await bitcoinWallet.getFeeRate();
629
+ }
630
+
631
+ const basePsbt = new Transaction({
632
+ allowUnknownOutputs: true,
633
+ allowLegacyWitnessUtxo: true
634
+ });
635
+ basePsbt.addOutput({
636
+ amount: this.amount,
637
+ script: toOutputScript(this.wrapper._options.bitcoinNetwork, this.address)
638
+ });
639
+ if(additionalOutputs!=null) additionalOutputs.forEach(output => {
640
+ basePsbt.addOutput({
641
+ amount: output.amount,
642
+ script: (output as {outputScript: Uint8Array}).outputScript ?? toOutputScript(this.wrapper._options.bitcoinNetwork, (output as {address: string}).address)
643
+ });
644
+ });
645
+
646
+ const psbt = await bitcoinWallet.fundPsbt(basePsbt, feeRate);
647
+ //Sign every input
648
+ const signInputs: number[] = [];
649
+ for(let i=0;i<psbt.inputsLength;i++) {
650
+ signInputs.push(i);
651
+ }
652
+ const serializedPsbt = Buffer.from(psbt.toPSBT());
653
+ return {
654
+ psbt,
655
+ psbtHex: serializedPsbt.toString("hex"),
656
+ psbtBase64: serializedPsbt.toString("base64"),
657
+ signInputs,
658
+ feeRate
659
+ };
660
+ }
661
+
662
+ /**
663
+ * @inheritDoc
664
+ */
665
+ getFundedPsbt(
666
+ _bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface,
667
+ feeRate?: number,
668
+ additionalOutputs?: ({amount: bigint, outputScript: Uint8Array} | {amount: bigint, address: string})[]
669
+ ) {
670
+ if(this._state!==FromBTCSwapState.CLAIM_COMMITED)
671
+ throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
672
+ if(this.txId!=null)
673
+ throw new Error("Bitcoin transaction already submitted for this swap!");
674
+ return this._getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs);
675
+ }
676
+
677
+ /**
678
+ * @inheritDoc
679
+ *
680
+ * @throws {Error} if the swap is in invalid state (not in {@link FromBTCSwapState.CLAIM_COMMITED}), or if
681
+ * the swap bitcoin address already expired.
682
+ */
683
+ async submitPsbt(_psbt: Transaction | string): Promise<string> {
684
+ const psbt = parsePsbtTransaction(_psbt);
685
+ if(this._state!==FromBTCSwapState.CLAIM_COMMITED)
686
+ throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
687
+ if(this.txId!=null)
688
+ throw new Error("Bitcoin transaction already submitted for this swap!");
689
+
690
+ //Ensure not expired
691
+ if(this.getTimeoutTime()<Date.now()) {
692
+ throw new Error("Swap address expired!");
693
+ }
694
+
695
+ const output0 = psbt.getOutput(0);
696
+ if(this.amount!=null && output0.amount!==this.amount)
697
+ throw new Error("PSBT output amount invalid, expected: "+this.amount+" got: "+output0.amount);
698
+ if(this.address!=null) {
699
+ const expectedOutputScript = toOutputScript(this.wrapper._options.bitcoinNetwork, this.address);
700
+ if(output0.script==null || !expectedOutputScript.equals(output0.script))
701
+ throw new Error("PSBT output script invalid!");
702
+ }
703
+
704
+ if(!psbt.isFinal) psbt.finalize();
705
+
706
+ const txId = await this.wrapper._btcRpc.sendRawTransaction(Buffer.from(psbt.toBytes(true, true)).toString("hex"));
707
+ await this._setSubmittedBitcoinTx(txId, psbt);
708
+ return txId;
709
+ }
710
+
711
+ /**
712
+ * @inheritDoc
713
+ */
714
+ async estimateBitcoinFee(_bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface, feeRate?: number): Promise<TokenAmount<BtcToken<false>, true> | null> {
715
+ if(this.address==null || this.amount==null) return null;
716
+ const bitcoinWallet: IBitcoinWallet = toBitcoinWallet(_bitcoinWallet, this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork);
717
+ const txFee = await bitcoinWallet.getTransactionFee(this.address, this.amount, feeRate);
718
+ if(txFee==null) return null;
719
+ return toTokenAmount(BigInt(txFee), BitcoinTokens.BTC, this.wrapper._prices);
720
+ }
721
+
722
+ /**
723
+ * @inheritDoc
724
+ */
725
+ async sendBitcoinTransaction(wallet: IBitcoinWallet | MinimalBitcoinWalletInterfaceWithSigner, feeRate?: number): Promise<string> {
726
+ 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.");
727
+
728
+ if(this._state!==FromBTCSwapState.CLAIM_COMMITED)
729
+ throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
730
+ if(this.txId!=null)
731
+ throw new Error("Bitcoin transaction already submitted for this swap!");
732
+
733
+ //Ensure not expired
734
+ if(this.getTimeoutTime()<Date.now()) {
735
+ throw new Error("Swap address expired!");
736
+ }
737
+
738
+ if(isIBitcoinWallet(wallet)) {
739
+ const txId = await wallet.sendTransaction(this.address, this.amount, feeRate);
740
+ await this._setSubmittedBitcoinTx(txId);
741
+ return txId;
742
+ } else {
743
+ const {psbt, psbtHex, psbtBase64, signInputs} = await this.getFundedPsbt(wallet, feeRate);
744
+ const signedPsbt = await wallet.signPsbt({
745
+ psbt, psbtHex, psbtBase64
746
+ }, signInputs);
747
+ return await this.submitPsbt(signedPsbt);
748
+ }
749
+ }
750
+
751
+
752
+ //////////////////////////////
753
+ //// Execution
754
+
755
+ /**
756
+ * Executes the swap with the provided bitcoin wallet,
757
+ *
758
+ * @param dstSigner Signer on the destination network, needs to have the same address as the one specified when
759
+ * quote was created, this is required for legacy swaps because the destination wallet needs to actively open
760
+ * a bitcoin swap address to which the BTC is then sent, this means that the address also needs to have enough
761
+ * native tokens to pay for gas on the destination network
762
+ * @param wallet Bitcoin wallet to use to sign the bitcoin transaction, can also be null - then the execution waits
763
+ * till a transaction is received from an external wallet
764
+ * @param callbacks Callbacks to track the progress of the swap
765
+ * @param options Optional options for the swap like feeRate, AbortSignal, and timeouts/intervals
766
+ *
767
+ * @returns {boolean} Whether a swap was settled automatically by swap watchtowers or requires manual claim by the
768
+ * user, in case `false` is returned the user should call `swap.claim()` to settle the swap on the destination manually
769
+ */
770
+ async execute(
771
+ dstSigner: T["Signer"] | T["NativeSigner"],
772
+ wallet?: IBitcoinWallet | MinimalBitcoinWalletInterfaceWithSigner | null | undefined,
773
+ callbacks?: {
774
+ onDestinationCommitSent?: (destinationCommitTxId: string) => void,
775
+ onSourceTransactionSent?: (sourceTxId: string) => void,
776
+ onSourceTransactionConfirmationStatus?: (sourceTxId?: string, confirmations?: number, targetConfirations?: number, etaMs?: number) => void,
777
+ onSourceTransactionConfirmed?: (sourceTxId: string) => void,
778
+ onSwapSettled?: (destinationTxId: string) => void
779
+ },
780
+ options?: {
781
+ feeRate?: number,
782
+ abortSignal?: AbortSignal,
783
+ btcTxCheckIntervalSeconds?: number,
784
+ maxWaitTillAutomaticSettlementSeconds?: number
785
+ }
786
+ ): Promise<boolean> {
787
+ if(this._state===FromBTCSwapState.FAILED) throw new Error("Swap failed!");
788
+ if(this._state===FromBTCSwapState.EXPIRED) throw new Error("Swap address expired!");
789
+ if(this._state===FromBTCSwapState.QUOTE_EXPIRED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) throw new Error("Swap quote expired!");
790
+ if(this._state===FromBTCSwapState.CLAIM_CLAIMED) throw new Error("Swap already settled!");
791
+
792
+ if(this._state===FromBTCSwapState.PR_CREATED) {
793
+ await this.commit(dstSigner, options?.abortSignal, undefined, callbacks?.onDestinationCommitSent);
794
+ }
795
+ if(this._state===FromBTCSwapState.CLAIM_COMMITED) {
796
+ if(wallet!=null) {
797
+ const bitcoinPaymentSent = await this.getBitcoinPayment();
798
+
799
+ if(bitcoinPaymentSent==null && this.txId==null) {
800
+ //Send btc tx
801
+ const txId = await this.sendBitcoinTransaction(wallet, options?.feeRate);
802
+ if(callbacks?.onSourceTransactionSent!=null) callbacks.onSourceTransactionSent(txId);
803
+ }
804
+ }
805
+
806
+ const txId = await this.waitForBitcoinTransaction(callbacks?.onSourceTransactionConfirmationStatus, options?.btcTxCheckIntervalSeconds, options?.abortSignal);
807
+ if (callbacks?.onSourceTransactionConfirmed != null) callbacks.onSourceTransactionConfirmed(txId);
808
+ }
809
+
810
+ // @ts-ignore
811
+ if(this._state===FromBTCSwapState.CLAIM_CLAIMED) return true;
812
+
813
+ if(this._state===FromBTCSwapState.BTC_TX_CONFIRMED) {
814
+ const success = await this.waitTillClaimed(options?.maxWaitTillAutomaticSettlementSeconds ?? 60, options?.abortSignal);
815
+ if(success && callbacks?.onSwapSettled!=null) callbacks.onSwapSettled(this.getOutputTxId()!);
816
+ return success;
817
+ }
818
+
819
+ throw new Error("Invalid state reached!");
820
+ }
821
+
822
+ /**
823
+ * @internal
824
+ */
825
+ protected async _getExecutionStatus(options?: {
826
+ maxWaitTillAutomaticSettlementSeconds?: number
827
+ }) {
828
+ const state = this._state;
829
+ const now = Date.now();
830
+ const timeoutTime = this.getTimeoutTime();
831
+
832
+ let confirmations: {
833
+ current: number,
834
+ target: number,
835
+ etaSeconds: number
836
+ } | undefined;
837
+ let bitcoinTxId: string | undefined;
838
+
839
+ let destinationSetupStatus: SwapExecutionStepSetup<T["ChainId"]>["status"] = "awaiting";
840
+ let bitcoinPaymentStatus: SwapExecutionStepPayment<"BITCOIN">["status"] = "inactive";
841
+ let destinationSettlementStatus: SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">["status"] = "inactive";
842
+ let buildCurrentAction: (actionOptions?: {
843
+ bitcoinFeeRate?: number,
844
+ bitcoinWallet?: MinimalBitcoinWalletInterface,
845
+ skipChecks?: boolean,
846
+ manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
847
+ }) => Promise<
848
+ SwapExecutionActionSendToAddress<false> |
849
+ SwapExecutionActionSignPSBT<"FUNDED_PSBT"> |
850
+ SwapExecutionActionWait<"BITCOIN_CONFS" | "SETTLEMENT"> |
851
+ SwapExecutionActionSignSmartChainTx<T> |
852
+ undefined
853
+ > = async () => undefined;
854
+
855
+ switch(state) {
856
+ case FromBTCSwapState.PR_CREATED: {
857
+ const quoteValid = await this._verifyQuoteValid();
858
+ destinationSetupStatus = quoteValid && timeoutTime>=now ? "awaiting" : "soft_expired";
859
+ if(quoteValid && timeoutTime>=now) {
860
+ buildCurrentAction = this._buildInitSmartChainTxAction.bind(this);
861
+ }
862
+ break;
863
+ }
864
+ case FromBTCSwapState.QUOTE_SOFT_EXPIRED:
865
+ destinationSetupStatus = "soft_expired";
866
+ break;
867
+ case FromBTCSwapState.QUOTE_EXPIRED:
868
+ destinationSetupStatus = "expired";
869
+ break;
870
+ case FromBTCSwapState.CLAIM_COMMITED:
871
+ case FromBTCSwapState.EXPIRED:
872
+ case FromBTCSwapState.FAILED:
873
+ const bitcoinPayment = this.address==null ? null : await this.getBitcoinPayment();
874
+ bitcoinTxId = bitcoinPayment?.txId;
875
+ let bitcoinConfirmationDelay: number | undefined;
876
+ if(bitcoinPayment!=null && bitcoinPayment.confirmations < bitcoinPayment.targetConfirmations) {
877
+ const tx = await this.wrapper._btcRpc.getTransaction(bitcoinPayment.txId);
878
+ const result = tx==null
879
+ ? null
880
+ : await this.wrapper._btcRpc.getConfirmationDelay(tx, bitcoinPayment.targetConfirmations);
881
+ bitcoinConfirmationDelay = result ?? -1;
882
+ }
883
+
884
+ destinationSetupStatus = "completed";
885
+ if(bitcoinPayment==null) {
886
+ if(this.txId!=null) {
887
+ bitcoinPaymentStatus = state===FromBTCSwapState.FAILED ? "expired" : "received";
888
+ if(state!==FromBTCSwapState.FAILED) {
889
+ buildCurrentAction = this._buildWaitBitcoinConfirmationsAction.bind(this, -1, "Wait for bitcoin transaction to be picked up by the RPC and confirmed.");
890
+ }
891
+ } else {
892
+ bitcoinPaymentStatus = "awaiting";
893
+ if(state===FromBTCSwapState.EXPIRED) bitcoinPaymentStatus = "soft_expired";
894
+ if(state===FromBTCSwapState.FAILED) bitcoinPaymentStatus = "expired";
895
+ if(
896
+ state===FromBTCSwapState.CLAIM_COMMITED && timeoutTime>=now &&
897
+ this.address!=null && this.amount!=null
898
+ ) {
899
+ buildCurrentAction = this._buildSendToAddressOrSignPsbtAction.bind(this);
900
+ }
901
+ }
902
+ } else if(bitcoinPayment.confirmations >= bitcoinPayment.targetConfirmations) {
903
+ bitcoinPaymentStatus = "confirmed";
904
+ if(state!==FromBTCSwapState.FAILED) {
905
+ buildCurrentAction = this._buildWaitBitcoinConfirmationsAction.bind(this, bitcoinConfirmationDelay ?? -1, undefined);
906
+ }
907
+ } else {
908
+ bitcoinPaymentStatus = "received";
909
+ confirmations = {
910
+ current: bitcoinPayment.confirmations,
911
+ target: bitcoinPayment.targetConfirmations,
912
+ etaSeconds: bitcoinConfirmationDelay ?? -1
913
+ };
914
+ if(state!==FromBTCSwapState.FAILED) {
915
+ buildCurrentAction = this._buildWaitBitcoinConfirmationsAction.bind(this, bitcoinConfirmationDelay ?? -1, undefined);
916
+ }
917
+ }
918
+ destinationSettlementStatus = state===FromBTCSwapState.FAILED ? "expired" : "inactive";
919
+ break;
920
+ case FromBTCSwapState.BTC_TX_CONFIRMED:
921
+ destinationSetupStatus = "completed";
922
+ bitcoinPaymentStatus = "confirmed";
923
+ if(
924
+ this.btcTxConfirmedAt==null ||
925
+ options?.maxWaitTillAutomaticSettlementSeconds===0 ||
926
+ (now - this.btcTxConfirmedAt) > (options?.maxWaitTillAutomaticSettlementSeconds ?? 60)*1000
927
+ ) {
928
+ destinationSettlementStatus = "awaiting_manual";
929
+ buildCurrentAction = this._buildClaimSmartChainTxAction.bind(this);
930
+ } else {
931
+ destinationSettlementStatus = "awaiting_automatic";
932
+ buildCurrentAction = this._buildWaitSettlementAction.bind(this, options?.maxWaitTillAutomaticSettlementSeconds);
933
+ }
934
+ break;
935
+ case FromBTCSwapState.CLAIM_CLAIMED:
936
+ destinationSetupStatus = "completed";
937
+ bitcoinPaymentStatus = "confirmed";
938
+ destinationSettlementStatus = "settled";
939
+ break;
940
+ }
941
+
942
+ if(bitcoinPaymentStatus==="confirmed") {
943
+ const requiredConfirmations = this.getRequiredConfirmationsCount();
944
+ if(!Number.isNaN(requiredConfirmations)) {
945
+ confirmations = {
946
+ current: requiredConfirmations,
947
+ target: requiredConfirmations,
948
+ etaSeconds: 0
949
+ };
950
+ }
951
+ }
952
+
953
+ return {
954
+ steps: [
955
+ {
956
+ type: "Setup",
957
+ side: "destination",
958
+ chain: this.chainIdentifier,
959
+ title: "Open Bitcoin swap address",
960
+ description: `Create the escrow on the ${this.chainIdentifier} side to open the Bitcoin swap address`,
961
+ status: destinationSetupStatus,
962
+ setupTxId: this._commitTxId
963
+ },
964
+ {
965
+ type: "Payment",
966
+ side: "source",
967
+ chain: "BITCOIN",
968
+ title: "Bitcoin payment",
969
+ description: "Send Bitcoin to the swap address and wait for the transaction to confirm",
970
+ status: bitcoinPaymentStatus,
971
+ confirmations,
972
+ initTxId: this.txId ?? bitcoinTxId,
973
+ settleTxId: this.txId
974
+ },
975
+ {
976
+ type: "Settlement",
977
+ side: "destination",
978
+ chain: this.chainIdentifier,
979
+ title: "Destination settlement",
980
+ description: `Wait for automatic settlement on the ${this.chainIdentifier} side, or settle manually if it takes too long`,
981
+ status: destinationSettlementStatus,
982
+ initTxId: this._commitTxId,
983
+ settleTxId: this._claimTxId
984
+ }
985
+ ] as [
986
+ SwapExecutionStepSetup<T["ChainId"]>,
987
+ SwapExecutionStepPayment<"BITCOIN">,
988
+ SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
989
+ ],
990
+ buildCurrentAction,
991
+ state
992
+ };
993
+ }
994
+
995
+ /**
996
+ * @inheritDoc
997
+ * @internal
998
+ */
999
+ async _submitExecutionTransactions(txs: (T["SignedTXType"] | Transaction | string)[], abortSignal?: AbortSignal, requiredStates?: FromBTCSwapState[], idempotent?: boolean): Promise<string[]> {
1000
+ if(txs.length===0) throw new Error("Need to submit at least 1 transaction in the array, submitted empty array of transactions!");
1001
+
1002
+ if(idempotent) {
1003
+ // Handle idempotent calls
1004
+ let idempotencyTriggered = false;
1005
+ const txIds: string[] = [];
1006
+ for(let tx of txs) {
1007
+ let parsedTx: T["SignedTXType"] | Transaction | undefined;
1008
+ if(typeof(tx)==="string") {
1009
+ try {
1010
+ parsedTx = await this.wrapper._chain.deserializeSignedTx(tx);
1011
+ } catch (e) {}
1012
+ try {
1013
+ parsedTx = parsePsbtTransaction(tx);
1014
+ } catch (e) {}
1015
+ } else {
1016
+ parsedTx = tx;
1017
+ }
1018
+
1019
+ if(parsedTx==null) {
1020
+ this.logger.debug("_submitExecutionTransactions(): Failed to parse provided execution transaction: ", tx);
1021
+ continue;
1022
+ }
1023
+
1024
+ if(parsedTx instanceof Transaction) {
1025
+ // Bitcoin tx
1026
+ const btcTx = await this.wrapper._btcRpc.parseTransaction(Buffer.from(parsedTx.toBytes(true)).toString("hex"));
1027
+ if(btcTx.txid===this.txId) idempotencyTriggered = true;
1028
+ txIds.push(btcTx.txid);
1029
+ } else {
1030
+ // SC tx
1031
+ if(this.wrapper._chain.getTxId!=null) {
1032
+ const txId = await this.wrapper._chain.getTxId(parsedTx);
1033
+ if(this._commitTxId===txId || this._claimTxId===txId) idempotencyTriggered = true;
1034
+ txIds.push(txId);
1035
+ }
1036
+ }
1037
+ }
1038
+ if(idempotencyTriggered) return txIds;
1039
+ }
1040
+
1041
+ if(requiredStates!=null && !requiredStates.includes(this._state)) throw new Error("Swap state has changed before transactions were submitted!");
1042
+
1043
+ if(this._state===FromBTCSwapState.CLAIM_COMMITED) {
1044
+ let psbt: string | Transaction;
1045
+ if(txs.length!==1) throw new Error("Need to submit exactly 1 signed PSBT!");
1046
+ if(typeof(txs[0])!=="string" && !(txs[0] instanceof Transaction))
1047
+ throw new Error("Must submit a valid PSBT as hex/base64 string or `@scure/btc-signer` Transaction object!");
1048
+ psbt = txs[0];
1049
+ return [await this.submitPsbt(psbt)];
1050
+ }
1051
+
1052
+ if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
1053
+ if(!await this._verifyQuoteValid()) throw new Error("Quote is already expired!");
1054
+ if(this.getTimeoutTime()<Date.now()) throw new Error("Swap address already expired or close to expiry!");
1055
+
1056
+ const parsedTxs: T["SignedTXType"][] = [];
1057
+ for(let tx of txs) {
1058
+ parsedTxs.push(typeof(tx)==="string" ? await this.wrapper._chain.deserializeSignedTx(tx) : tx);
1059
+ }
1060
+ const txIds = await this.wrapper._chain.sendSignedAndConfirm(parsedTxs, true, abortSignal, false);
1061
+ await this.waitTillCommited(abortSignal);
1062
+ return txIds;
1063
+ }
1064
+
1065
+ if(this._state===FromBTCSwapState.BTC_TX_CONFIRMED) {
1066
+ const parsedTxs: T["SignedTXType"][] = [];
1067
+ for(let tx of txs) {
1068
+ parsedTxs.push(typeof(tx)==="string" ? await this.wrapper._chain.deserializeSignedTx(tx) : tx);
1069
+ }
1070
+ const txIds = await this.wrapper._chain.sendSignedAndConfirm(parsedTxs, true, abortSignal, false);
1071
+ await this.waitTillClaimed(undefined, abortSignal);
1072
+ return txIds;
1073
+ }
1074
+
1075
+ throw new Error("Invalid swap state for transaction submission!");
1076
+ }
1077
+
1078
+ /**
1079
+ * @internal
1080
+ */
1081
+ private async _buildSendToAddressOrSignPsbtAction(actionOptions?: {
1082
+ bitcoinFeeRate?: number,
1083
+ bitcoinWallet?: MinimalBitcoinWalletInterface,
1084
+ }): Promise<
1085
+ SwapExecutionActionSendToAddress<false> |
1086
+ SwapExecutionActionSignPSBT<"FUNDED_PSBT">
1087
+ > {
1088
+ if(this.address==null) throw new Error("Bitcoin swap address not known!");
1089
+ if(this.amount==null) throw new Error("Bitcoin swap amount not known!");
1090
+
1091
+ if(actionOptions?.bitcoinWallet==null) {
1092
+ return {
1093
+ type: "SendToAddress",
1094
+ name: "Deposit on Bitcoin",
1095
+ description: "Send funds to the bitcoin swap address",
1096
+ chain: "BITCOIN",
1097
+ txs: [{
1098
+ type: "BITCOIN_ADDRESS",
1099
+ address: this.address,
1100
+ hyperlink: this._getHyperlink(),
1101
+ amount: toTokenAmount(this.amount, BitcoinTokens.BTC, this.wrapper._prices)
1102
+ }],
1103
+ waitForTransactions: async (
1104
+ maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal
1105
+ ) => {
1106
+ let btcTxId: string | undefined;
1107
+ const abortController = extendAbortController(
1108
+ abortSignal, maxWaitTimeSeconds, "Timed out waiting for bitcoin transaction"
1109
+ );
1110
+
1111
+ try {
1112
+ return await this.waitForBitcoinTransaction(
1113
+ (txId) => {
1114
+ btcTxId = txId;
1115
+ abortController.abort();
1116
+ },
1117
+ pollIntervalSeconds,
1118
+ abortController.signal
1119
+ );
1120
+ } catch (e) {
1121
+ if(btcTxId!=null) return btcTxId;
1122
+ throw e;
1123
+ }
1124
+ }
1125
+ } as SwapExecutionActionSendToAddress<false>;
1126
+ }
1127
+
1128
+ return {
1129
+ type: "SignPSBT",
1130
+ name: "Deposit on Bitcoin",
1131
+ description: "Send funds to the bitcoin swap address",
1132
+ chain: "BITCOIN",
1133
+ txs: [{
1134
+ ...await this.getFundedPsbt(actionOptions.bitcoinWallet, actionOptions?.bitcoinFeeRate),
1135
+ type: "FUNDED_PSBT"
1136
+ }],
1137
+ submitPsbt: async (signedPsbt: string | Transaction | (string | Transaction)[], idempotent?: boolean) => {
1138
+ return this._submitExecutionTransactions(
1139
+ Array.isArray(signedPsbt) ? signedPsbt : [signedPsbt],
1140
+ undefined,
1141
+ [FromBTCSwapState.CLAIM_COMMITED],
1142
+ idempotent
1143
+ );
1144
+ }
1145
+ } as SwapExecutionActionSignPSBT<"FUNDED_PSBT">;
1146
+ }
1147
+
1148
+ /**
1149
+ * @internal
1150
+ */
1151
+ private async _buildWaitBitcoinConfirmationsAction(confirmationDelay: number, description?: string): Promise<SwapExecutionActionWait<"BITCOIN_CONFS">> {
1152
+ return {
1153
+ type: "Wait",
1154
+ name: "Bitcoin confirmations",
1155
+ description: description ?? "Wait for bitcoin transaction to confirm",
1156
+ pollTimeSeconds: 10,
1157
+ expectedTimeSeconds: confirmationDelay===-1 ? -1 : Math.floor(confirmationDelay/1000),
1158
+ wait: async (
1159
+ maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal,
1160
+ btcConfirmationsCallback?: (txId?: string, confirmations?: number, targetConfirmations?: number, txEtaMs?: number) => void
1161
+ ) => {
1162
+ const abortController = extendAbortController(
1163
+ abortSignal, maxWaitTimeSeconds, "Timed out waiting for bitcoin transaction to confirm"
1164
+ );
1165
+ await this.waitForBitcoinTransaction(btcConfirmationsCallback, pollIntervalSeconds, abortController.signal);
1166
+ }
1167
+ } as SwapExecutionActionWait<"BITCOIN_CONFS">;
1168
+ }
1169
+
1170
+ /**
1171
+ * @internal
1172
+ */
1173
+ private async _buildWaitSettlementAction(maxWaitTillAutomaticSettlementSeconds?: number): Promise<SwapExecutionActionWait<"SETTLEMENT">> {
1174
+ return {
1175
+ type: "Wait",
1176
+ name: "Automatic settlement",
1177
+ description: "Wait for automatic settlement by the watchtower",
1178
+ pollTimeSeconds: 5,
1179
+ expectedTimeSeconds: 10,
1180
+ wait: async (
1181
+ maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal
1182
+ ) => {
1183
+ await this.waitTillClaimed(maxWaitTimeSeconds ?? maxWaitTillAutomaticSettlementSeconds ?? 60, abortSignal, pollIntervalSeconds);
1184
+ }
1185
+ } as SwapExecutionActionWait<"SETTLEMENT">;
1186
+ }
1187
+
1188
+ /**
1189
+ * @internal
1190
+ */
1191
+ private async _buildInitSmartChainTxAction(actionOptions?: {
1192
+ skipChecks?: boolean,
1193
+ }): Promise<SwapExecutionActionSignSmartChainTx<T>> {
1194
+ return {
1195
+ type: "SignSmartChainTransaction",
1196
+ name: "Initiate swap",
1197
+ description: `Opens up the bitcoin swap address on the ${this.chainIdentifier} side`,
1198
+ chain: this.chainIdentifier,
1199
+ txs: await this.prepareTransactions(this.txsCommit(actionOptions?.skipChecks)),
1200
+ submitTransactions: async (txs: (T["SignedTXType"] | string)[], abortSignal?: AbortSignal, idempotent?: boolean) => {
1201
+ return this._submitExecutionTransactions(
1202
+ txs,
1203
+ abortSignal,
1204
+ [FromBTCSwapState.PR_CREATED, FromBTCSwapState.QUOTE_SOFT_EXPIRED],
1205
+ idempotent
1206
+ );
1207
+ },
1208
+ requiredSigner: this._getInitiator()
1209
+ } as SwapExecutionActionSignSmartChainTx<T>;
1210
+ }
1211
+
1212
+ /**
1213
+ * @inheritDoc
1214
+ * @internal
1215
+ */
1216
+ private async _buildClaimSmartChainTxAction(actionOptions?: {
1217
+ manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
1218
+ }): Promise<SwapExecutionActionSignSmartChainTx<T>> {
1219
+ const signerAddress =
1220
+ await this.wrapper._getSignerAddress(actionOptions?.manualSettlementSmartChainSigner);
1221
+
1222
+ return {
1223
+ type: "SignSmartChainTransaction",
1224
+ name: "Settle manually",
1225
+ description: "Manually settle (claim) the swap on the destination smart chain",
1226
+ chain: this.chainIdentifier,
1227
+ txs: await this.prepareTransactions(this.txsClaim(actionOptions?.manualSettlementSmartChainSigner)),
1228
+ submitTransactions: async (txs: (T["SignedTXType"] | string)[], abortSignal?: AbortSignal, idempotent?: boolean) => {
1229
+ return this._submitExecutionTransactions(
1230
+ txs,
1231
+ abortSignal,
1232
+ [FromBTCSwapState.BTC_TX_CONFIRMED],
1233
+ idempotent
1234
+ );
1235
+ },
1236
+ requiredSigner: signerAddress ?? this._getInitiator()
1237
+ } as SwapExecutionActionSignSmartChainTx<T>;
1238
+ }
1239
+
1240
+ /**
1241
+ * @inheritDoc
1242
+ *
1243
+ * @param options.bitcoinFeeRate Optional fee rate to use for the created Bitcoin transaction
1244
+ * @param options.bitcoinWallet Bitcoin wallet to use, when provided the function returns a funded
1245
+ * psbt (`"FUNDED_PSBT"`), if not passed just a bitcoin receive address is returned (`"ADDRESS"`)
1246
+ * @param options.skipChecks Skip checks like making sure init signature is still valid and swap
1247
+ * wasn't commited yet (this is handled on swap creation, if you commit right after quoting, you
1248
+ * can use `skipChecks=true`)
1249
+ * @param options.manualSettlementSmartChainSigner Optional smart chain signer to create a manual claim (settlement) transaction
1250
+ * @param options.maxWaitTillAutomaticSettlementSeconds Maximum time to wait for an automatic settlement after
1251
+ * the bitcoin transaction is confirmed (defaults to 60 seconds)
1252
+ */
1253
+ async getExecutionAction(options?: {
1254
+ bitcoinFeeRate?: number,
1255
+ bitcoinWallet?: MinimalBitcoinWalletInterface,
1256
+ skipChecks?: boolean,
1257
+ manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
1258
+ maxWaitTillAutomaticSettlementSeconds?: number
1259
+ }): Promise<
1260
+ SwapExecutionActionSendToAddress<false> |
1261
+ SwapExecutionActionSignPSBT<"FUNDED_PSBT"> |
1262
+ SwapExecutionActionWait<"BITCOIN_CONFS" | "SETTLEMENT"> |
1263
+ SwapExecutionActionSignSmartChainTx<T> |
1264
+ undefined
1265
+ > {
1266
+ const executionStatus = await this._getExecutionStatus(options);
1267
+ return executionStatus.buildCurrentAction(options);
1268
+ }
1269
+
1270
+ /**
1271
+ * @inheritDoc
1272
+ */
1273
+ async getExecutionStatus(options?: {
1274
+ skipBuildingAction?: boolean,
1275
+ bitcoinFeeRate?: number,
1276
+ bitcoinWallet?: MinimalBitcoinWalletInterface,
1277
+ skipChecks?: boolean,
1278
+ manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
1279
+ maxWaitTillAutomaticSettlementSeconds?: number
1280
+ }): Promise<{
1281
+ steps: [
1282
+ SwapExecutionStepSetup<T["ChainId"]>,
1283
+ SwapExecutionStepPayment<"BITCOIN">,
1284
+ SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
1285
+ ],
1286
+ currentAction:
1287
+ SwapExecutionActionSendToAddress<false> |
1288
+ SwapExecutionActionSignPSBT<"FUNDED_PSBT"> |
1289
+ SwapExecutionActionWait<"BITCOIN_CONFS" | "SETTLEMENT"> |
1290
+ SwapExecutionActionSignSmartChainTx<T> |
1291
+ undefined,
1292
+ stateInfo: SwapStateInfo<FromBTCSwapState>
1293
+ }> {
1294
+ const executionStatus = await this._getExecutionStatus(options);
1295
+ return {
1296
+ steps: executionStatus.steps,
1297
+ currentAction: options?.skipBuildingAction ? undefined : await executionStatus.buildCurrentAction(options),
1298
+ stateInfo: this._getStateInfo(executionStatus.state)
1299
+ };
1300
+ }
1301
+
1302
+ /**
1303
+ * @inheritDoc
1304
+ */
1305
+ async getExecutionSteps(options?: {
1306
+ maxWaitTillAutomaticSettlementSeconds?: number
1307
+ }): Promise<[
1308
+ SwapExecutionStepSetup<T["ChainId"]>,
1309
+ SwapExecutionStepPayment<"BITCOIN">,
1310
+ SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
1311
+ ]> {
1312
+ return (await this._getExecutionStatus(options)).steps;
1313
+ }
1314
+
1315
+ //////////////////////////////
1316
+ //// Commit
1317
+
1318
+ /**
1319
+ * @inheritDoc
1320
+ *
1321
+ * @throws {Error} If invalid signer is provided that doesn't match the swap data
1322
+ */
1323
+ async commit(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, skipChecks?: boolean, onBeforeTxSent?: (txId: string) => void): Promise<string> {
1324
+ const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
1325
+ this.checkSigner(signer);
1326
+ let txCount = 0;
1327
+ const txs = await this.txsCommit(skipChecks);
1328
+ const result = await this.wrapper._chain.sendAndConfirm(
1329
+ signer, txs, true, abortSignal, undefined, (txId: string) => {
1330
+ txCount++;
1331
+ if(onBeforeTxSent!=null && txCount===txs.length) onBeforeTxSent(txId);
1332
+ return Promise.resolve();
1333
+ }
1334
+ );
1335
+
1336
+ this._commitTxId = result[result.length - 1];
1337
+ if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED || this._state===FromBTCSwapState.QUOTE_EXPIRED) {
1338
+ await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
1339
+ }
1340
+ return this._commitTxId;
1341
+ }
1342
+
1343
+ /**
1344
+ * @inheritDoc
1345
+ */
1346
+ async waitTillCommited(abortSignal?: AbortSignal): Promise<void> {
1347
+ if(this._state===FromBTCSwapState.CLAIM_COMMITED || this._state===FromBTCSwapState.CLAIM_CLAIMED) return Promise.resolve();
1348
+ if(this._state!==FromBTCSwapState.PR_CREATED && this._state!==FromBTCSwapState.QUOTE_SOFT_EXPIRED) throw new Error("Invalid state");
1349
+
1350
+ const abortController = extendAbortController(abortSignal);
1351
+ const result = await Promise.race([
1352
+ this.watchdogWaitTillCommited(undefined, abortController.signal),
1353
+ this.waitTillState(FromBTCSwapState.CLAIM_COMMITED, "gte", abortController.signal).then(() => 0)
1354
+ ]);
1355
+ abortController.abort();
1356
+
1357
+ if(result===0) {
1358
+ this.logger.debug("waitTillCommited(): Resolved from state changed");
1359
+ } else if(result!=null) {
1360
+ this.logger.debug("waitTillCommited(): Resolved from watchdog - commited");
1361
+ }
1362
+
1363
+ if(result===null) {
1364
+ this.logger.debug("waitTillCommited(): Resolved from watchdog - signature expired");
1365
+ if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
1366
+ await this._saveAndEmit(FromBTCSwapState.QUOTE_EXPIRED);
1367
+ }
1368
+ return;
1369
+ }
1370
+
1371
+ if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
1372
+ if(typeof(result)==="object" && (result as any).getInitTxId!=null && this._commitTxId==null)
1373
+ this._commitTxId = await (result as any).getInitTxId();
1374
+ await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
1375
+ }
1376
+ }
1377
+
1378
+
1379
+ //////////////////////////////
1380
+ //// Claim
1381
+
1382
+ /**
1383
+ * Returns transactions for settling (claiming) the swap if the swap requires manual settlement, you can check so
1384
+ * with isClaimable. After sending the transaction manually be sure to call the waitTillClaimed function to wait
1385
+ * till the claim transaction is observed, processed by the SDK and state of the swap properly updated.
1386
+ *
1387
+ * @remarks
1388
+ * Might also return transactions necessary to sync the bitcoin light client.
1389
+ *
1390
+ * @param _signer Address of the signer to create the claim transactions for
1391
+ *
1392
+ * @throws {Error} If the swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
1393
+ */
1394
+ async txsClaim(_signer?: string | T["Signer"] | T["NativeSigner"]): Promise<T["TX"][]> {
1395
+ let signer: string | T["Signer"] | undefined = undefined;
1396
+ if(_signer!=null) {
1397
+ if (typeof (_signer) === "string") {
1398
+ signer = _signer;
1399
+ } else if (isAbstractSigner(_signer)) {
1400
+ signer = _signer;
1401
+ } else {
1402
+ signer = await this.wrapper._chain.wrapSigner(_signer);
1403
+ }
1404
+ }
1405
+
1406
+ if(this._state!==FromBTCSwapState.BTC_TX_CONFIRMED) throw new Error("Must be in BTC_TX_CONFIRMED state!");
1407
+ if(this.txId==null || this.vout==null) throw new Error("Bitcoin transaction ID not known!");
1408
+
1409
+ const tx = await this.wrapper._btcRpc.getTransaction(this.txId);
1410
+ if(tx==null) throw new Error("Bitcoin transaction not found on the network!");
1411
+
1412
+ this.requiredConfirmations ??= this.inferRequiredConfirmationsCount(tx, this.vout);
1413
+ if(this.requiredConfirmations==null)
1414
+ 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.");
1415
+
1416
+ if(tx.blockhash==null || tx.confirmations==null || tx.blockheight==null || tx.confirmations<this.requiredConfirmations)
1417
+ throw new Error("Bitcoin transaction not confirmed yet!");
1418
+
1419
+ return await this._contract.txsClaimWithTxData(signer ?? this._getInitiator(), this._data, {
1420
+ blockhash: tx.blockhash,
1421
+ confirmations: tx.confirmations,
1422
+ txid: tx.txid,
1423
+ hex: tx.hex,
1424
+ height: tx.blockheight
1425
+ }, this.requiredConfirmations, this.vout, undefined, this.wrapper._synchronizer(this._contractVersion), true);
1426
+ }
1427
+
1428
+ /**
1429
+ * Settles the swap by claiming the funds on the destination chain if the swap requires manual settlement, you can
1430
+ * check so with isClaimable.
1431
+ *
1432
+ * @remarks
1433
+ * Might also sync the bitcoin light client during the process.
1434
+ *
1435
+ * @param _signer Signer to use for signing the settlement transactions, can also be different to the recipient
1436
+ * @param abortSignal Abort signal
1437
+ * @param onBeforeTxSent Optional callback triggered before the claim transaction is broadcasted
1438
+ *
1439
+ * @returns Transaction ID of the settlement (claim) transaction on the destination smart chain
1440
+ */
1441
+ async claim(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, onBeforeTxSent?: (txId: string) => void): Promise<string> {
1442
+ const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
1443
+ let txIds: string[];
1444
+ try {
1445
+ let txCount = 0;
1446
+ const txs = await this.txsClaim(signer);
1447
+ txIds = await this.wrapper._chain.sendAndConfirm(
1448
+ signer, txs, true, abortSignal, undefined, (txId: string) => {
1449
+ txCount++;
1450
+ if(onBeforeTxSent!=null && txCount===txs.length) onBeforeTxSent(txId);
1451
+ return Promise.resolve();
1452
+ }
1453
+ );
1454
+ } catch (e) {
1455
+ this.logger.info("claim(): Failed to claim ourselves, checking swap claim state...");
1456
+ if(this._state===FromBTCSwapState.CLAIM_CLAIMED) {
1457
+ this.logger.info("claim(): Transaction state is CLAIM_CLAIMED, swap was successfully claimed by the watchtower");
1458
+ return this._claimTxId!;
1459
+ }
1460
+ const status = await this._contract.getCommitStatus(this._getInitiator(), this._data);
1461
+ if(status?.type===SwapCommitStateType.PAID) {
1462
+ this.logger.info("claim(): Transaction commit status is PAID, swap was successfully claimed by the watchtower");
1463
+ if(this._claimTxId==null) this._claimTxId = await status.getClaimTxId();
1464
+ const txId = Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
1465
+ await this._setBitcoinTxId(txId);
1466
+ await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
1467
+ return this._claimTxId;
1468
+ }
1469
+ throw e;
1470
+ }
1471
+
1472
+ this._claimTxId = txIds[txIds.length - 1];
1473
+ if(
1474
+ this._state===FromBTCSwapState.CLAIM_COMMITED || this._state===FromBTCSwapState.BTC_TX_CONFIRMED ||
1475
+ this._state===FromBTCSwapState.EXPIRED || this._state===FromBTCSwapState.FAILED
1476
+ ) {
1477
+ await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
1478
+ }
1479
+ return txIds[txIds.length - 1];
1480
+ }
1481
+
1482
+ /**
1483
+ * @inheritDoc
1484
+ *
1485
+ * @throws {Error} If swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
1486
+ * @throws {Error} If the LP refunded sooner than we were able to claim
1487
+ */
1488
+ async waitTillClaimed(maxWaitTimeSeconds?: number, abortSignal?: AbortSignal, pollIntervalSeconds?: number): Promise<boolean> {
1489
+ if(this._state===FromBTCSwapState.CLAIM_CLAIMED) return Promise.resolve(true);
1490
+ if(this._state!==FromBTCSwapState.BTC_TX_CONFIRMED) throw new Error("Invalid state (not BTC_TX_CONFIRMED)");
1491
+
1492
+ const abortController = extendAbortController(abortSignal);
1493
+
1494
+ let timedOut: boolean = false;
1495
+ if(maxWaitTimeSeconds!=null) {
1496
+ const timeout = setTimeout(() => {
1497
+ timedOut = true;
1498
+ abortController.abort();
1499
+ }, maxWaitTimeSeconds * 1000);
1500
+ abortController.signal.addEventListener("abort", () => clearTimeout(timeout));
1501
+ }
1502
+
1503
+ let res: 0 | 1 | SwapCommitState;
1504
+ try {
1505
+ res = await Promise.race([
1506
+ this.watchdogWaitTillResult(pollIntervalSeconds, abortController.signal),
1507
+ this.waitTillState(FromBTCSwapState.CLAIM_CLAIMED, "eq", abortController.signal).then(() => 0 as const),
1508
+ this.waitTillState(FromBTCSwapState.FAILED, "eq", abortController.signal).then(() => 1 as const),
1509
+ ]);
1510
+ abortController.abort();
1511
+ } catch (e) {
1512
+ abortController.abort();
1513
+ if(timedOut) return false;
1514
+ throw e;
1515
+ }
1516
+
1517
+ if(res===0) {
1518
+ this.logger.debug("waitTillClaimed(): Resolved from state change (CLAIM_CLAIMED)");
1519
+ return true;
1520
+ }
1521
+ if(res===1) {
1522
+ this.logger.debug("waitTillClaimed(): Resolved from state change (FAILED)");
1523
+ throw new Error("Offerer refunded during claiming");
1524
+ }
1525
+ this.logger.debug("waitTillClaimed(): Resolved from watchdog");
1526
+
1527
+ if(res?.type===SwapCommitStateType.PAID) {
1528
+ if((this._state as FromBTCSwapState)!==FromBTCSwapState.CLAIM_CLAIMED) {
1529
+ if(this._claimTxId==null) this._claimTxId = await res.getClaimTxId();
1530
+ const txId = Buffer.from(await res.getClaimResult(), "hex").reverse().toString("hex");
1531
+ await this._setBitcoinTxId(txId);
1532
+ await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
1533
+ }
1534
+ }
1535
+ if(res?.type===SwapCommitStateType.NOT_COMMITED || res?.type===SwapCommitStateType.EXPIRED) {
1536
+ if(
1537
+ (this._state as FromBTCSwapState)!==FromBTCSwapState.CLAIM_CLAIMED &&
1538
+ (this._state as FromBTCSwapState)!==FromBTCSwapState.FAILED
1539
+ ) {
1540
+ if(res.getRefundTxId!=null) this._refundTxId = await res.getRefundTxId();
1541
+ await this._saveAndEmit(FromBTCSwapState.FAILED);
1542
+ }
1543
+ throw new Error("Swap expired while waiting for claim!");
1544
+ }
1545
+
1546
+ return true;
1547
+ }
1548
+
1549
+
1550
+ //////////////////////////////
1551
+ //// Storage
1552
+
1553
+ /**
1554
+ * @inheritDoc
1555
+ */
1556
+ serialize(): any {
1557
+ return {
1558
+ ...super.serialize(),
1559
+ address: this.address,
1560
+ amount: this.amount==null ? null: this.amount.toString(10),
1561
+ requiredConfirmations: this.requiredConfirmations,
1562
+ senderAddress: this.senderAddress,
1563
+ txId: this.txId,
1564
+ vout: this.vout,
1565
+ btcTxConfirmedAt: this.btcTxConfirmedAt
1566
+ };
1567
+ }
1568
+
1569
+
1570
+ //////////////////////////////
1571
+ //// Swap ticks & sync
1572
+
1573
+ /**
1574
+ * Checks the swap's state on-chain and compares it to its internal state, updates/changes it according to on-chain
1575
+ * data
1576
+ *
1577
+ * @private
1578
+ */
1579
+ private async syncStateFromChain(quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState): Promise<boolean> {
1580
+ if(
1581
+ this._state===FromBTCSwapState.PR_CREATED ||
1582
+ this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
1583
+ this._state===FromBTCSwapState.CLAIM_COMMITED ||
1584
+ this._state===FromBTCSwapState.BTC_TX_CONFIRMED ||
1585
+ this._state===FromBTCSwapState.EXPIRED
1586
+ ) {
1587
+ let quoteExpired: boolean = false;
1588
+ if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
1589
+ quoteExpired = quoteDefinitelyExpired ?? await this._verifyQuoteDefinitelyExpired(); //Make sure we check for expiry here, to prevent race conditions
1590
+ }
1591
+
1592
+ const status = commitStatus ?? await this._contract.getCommitStatus(this._getInitiator(), this._data);
1593
+ if(status!=null && await this._forciblySetOnchainState(status)) return true;
1594
+
1595
+ if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
1596
+ if(quoteExpired) {
1597
+ this._state = FromBTCSwapState.QUOTE_EXPIRED;
1598
+ return true;
1599
+ }
1600
+ }
1601
+ }
1602
+
1603
+ return false;
1604
+ }
1605
+
1606
+ /**
1607
+ * @inheritDoc
1608
+ * @internal
1609
+ */
1610
+ _shouldFetchOnchainState(): boolean {
1611
+ return this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
1612
+ this._state===FromBTCSwapState.CLAIM_COMMITED || this._state===FromBTCSwapState.BTC_TX_CONFIRMED ||
1613
+ this._state===FromBTCSwapState.EXPIRED;
1614
+ }
1615
+
1616
+ /**
1617
+ * @inheritDoc
1618
+ * @internal
1619
+ */
1620
+ _shouldFetchExpiryStatus(): boolean {
1621
+ return this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED;
1622
+ }
1623
+
1624
+ /**
1625
+ * @inheritDoc
1626
+ * @internal
1627
+ */
1628
+ async _sync(save?: boolean, quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState): Promise<boolean> {
1629
+ const changed = await this.syncStateFromChain(quoteDefinitelyExpired, commitStatus);
1630
+ if(changed && save) await this._saveAndEmit();
1631
+ return changed;
1632
+ }
1633
+
1634
+ private btcTxLastChecked?: number;
1635
+
1636
+ /**
1637
+ * @inheritDoc
1638
+ * @internal
1639
+ */
1640
+ async _forciblySetOnchainState(status: SwapCommitState): Promise<boolean> {
1641
+ switch(status.type) {
1642
+ case SwapCommitStateType.PAID:
1643
+ if(this._commitTxId==null && (status as any).getInitTxId!=null) this._commitTxId = await (status as any).getInitTxId();
1644
+ if(this._claimTxId==null) this._claimTxId = await status.getClaimTxId();
1645
+ const txId = Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
1646
+ await this._setBitcoinTxId(txId);
1647
+ this._state = FromBTCSwapState.CLAIM_CLAIMED;
1648
+ return true;
1649
+ case SwapCommitStateType.NOT_COMMITED:
1650
+ let changed: boolean = false;
1651
+ if(this._commitTxId==null && (status as any).getInitTxId!=null) {
1652
+ this._commitTxId = await (status as any).getInitTxId();
1653
+ changed = true;
1654
+ }
1655
+ if(this._refundTxId==null && status.getRefundTxId) {
1656
+ this._refundTxId = await status.getRefundTxId();
1657
+ changed = true;
1658
+ }
1659
+ if(this._refundTxId!=null) {
1660
+ this._state = FromBTCSwapState.FAILED;
1661
+ changed = true;
1662
+ }
1663
+ return changed;
1664
+ case SwapCommitStateType.EXPIRED:
1665
+ if(this._commitTxId==null && (status as any).getInitTxId!=null) this._commitTxId = await (status as any).getInitTxId();
1666
+ if(this._refundTxId==null && status.getRefundTxId) this._refundTxId = await status.getRefundTxId();
1667
+ this._state = this._refundTxId==null ? FromBTCSwapState.QUOTE_EXPIRED : FromBTCSwapState.FAILED;
1668
+ return true;
1669
+ case SwapCommitStateType.COMMITED:
1670
+ let save: boolean = false;
1671
+ if(this._commitTxId==null && (status as any).getInitTxId!=null) {
1672
+ this._commitTxId = await (status as any).getInitTxId();
1673
+ save = true;
1674
+ }
1675
+ if(this._state!==FromBTCSwapState.CLAIM_COMMITED && this._state!==FromBTCSwapState.BTC_TX_CONFIRMED && this._state!==FromBTCSwapState.EXPIRED) {
1676
+ this._state = FromBTCSwapState.CLAIM_COMMITED;
1677
+ save = true;
1678
+ }
1679
+ if(this.address==null) return save;
1680
+
1681
+ this.btcTxLastChecked = Date.now();
1682
+ const res = await this.getBitcoinPayment();
1683
+ if(res!=null) {
1684
+ if(this.txId!==res.txId || this.vout!==res.vout || (res.inputAddresses!=null && this.senderAddress==null)) {
1685
+ if(res.inputAddresses!=null) this.senderAddress = res.inputAddresses[0];
1686
+ this.txId = res.txId;
1687
+ this.vout = res.vout;
1688
+ save = true;
1689
+ }
1690
+ if(this.requiredConfirmations!=null && res.confirmations>=this.requiredConfirmations) {
1691
+ this.btcTxConfirmedAt ??= Date.now();
1692
+ this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
1693
+ save = true;
1694
+ }
1695
+ }
1696
+ return save;
1697
+ }
1698
+ return false;
1699
+ }
1700
+
1701
+ /**
1702
+ * @inheritDoc
1703
+ * @internal
1704
+ */
1705
+ async _tick(save?: boolean): Promise<boolean> {
1706
+ switch(this._state) {
1707
+ case FromBTCSwapState.PR_CREATED:
1708
+ if(this.expiry<Date.now()) {
1709
+ this._state = FromBTCSwapState.QUOTE_SOFT_EXPIRED;
1710
+ if(save) await this._saveAndEmit();
1711
+ return true;
1712
+ }
1713
+ break;
1714
+ case FromBTCSwapState.CLAIM_COMMITED:
1715
+ if(this.getTimeoutTime()<Date.now()) {
1716
+ this._state = FromBTCSwapState.EXPIRED;
1717
+ if(save) await this._saveAndEmit();
1718
+ return true;
1719
+ }
1720
+ case FromBTCSwapState.EXPIRED:
1721
+ //Check if bitcoin payment was received at least every 2 minutes
1722
+ if(this.btcTxLastChecked==null || Date.now() - this.btcTxLastChecked > 120_000) {
1723
+ if(this.address!=null) try {
1724
+ this.btcTxLastChecked = Date.now();
1725
+ const res = await this.getBitcoinPayment();
1726
+ if(res!=null) {
1727
+ let shouldSave: boolean = false;
1728
+ if(this.txId!==res.txId || this.vout!==res.vout || (res.inputAddresses!=null && this.senderAddress==null)) {
1729
+ this.txId = res.txId;
1730
+ this.vout = res.vout;
1731
+ if(res.inputAddresses!=null) this.senderAddress = res.inputAddresses[0];
1732
+ shouldSave = true;
1733
+ }
1734
+ if(this.requiredConfirmations!=null && res.confirmations>=this.requiredConfirmations) {
1735
+ this.btcTxConfirmedAt ??= Date.now();
1736
+ this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
1737
+ if(save) await this._saveAndEmit();
1738
+ shouldSave = true;
1739
+ }
1740
+ if(shouldSave && save) await this._saveAndEmit();
1741
+ return shouldSave;
1742
+ }
1743
+ } catch (e) {
1744
+ this.logger.warn("tickSwap("+this.getIdentifierHashString()+"): ", e);
1745
+ }
1746
+ }
1747
+ break;
1748
+ }
1749
+
1750
+ return false;
1751
+ }
1752
+
1753
+ }