@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,1883 +1,1883 @@
1
- import {decode as bolt11Decode} from "@atomiqlabs/bolt11";
2
- import {SwapType} from "../../../../enums/SwapType";
3
- import {
4
- ChainSwapType,
5
- ChainType,
6
- isAbstractSigner,
7
- SwapClaimWitnessMessage,
8
- SwapCommitState,
9
- SwapCommitStateType,
10
- SwapData,
11
- } from "@atomiqlabs/base";
12
- import {Buffer} from "buffer";
13
- import {LNURL} from "../../../../lnurl/LNURL";
14
- import {UserError} from "../../../../errors/UserError";
15
- import {
16
- IntermediaryAPI,
17
- InvoiceStatusResponse,
18
- InvoiceStatusResponseCodes
19
- } from "../../../../intermediaries/apis/IntermediaryAPI";
20
- import {IntermediaryError} from "../../../../errors/IntermediaryError";
21
- import {extendAbortController, toBigInt} from "../../../../utils/Utils";
22
- import {Fee} from "../../../../types/fees/Fee";
23
- import {IAddressSwap} from "../../../IAddressSwap";
24
- import {FromBTCLNAutoDefinition, FromBTCLNAutoWrapper} from "./FromBTCLNAutoWrapper";
25
- import {ISwapWithGasDrop} from "../../../ISwapWithGasDrop";
26
- import {MinimalLightningNetworkWalletInterface} from "../../../../types/wallets/MinimalLightningNetworkWalletInterface";
27
- import {IClaimableSwap} from "../../../IClaimableSwap";
28
- import {IEscrowSwap, IEscrowSwapInit, isIEscrowSwapInit} from "../../IEscrowSwap";
29
- import {FeeType} from "../../../../enums/FeeType";
30
- import {ppmToPercentage} from "../../../../types/fees/PercentagePPM";
31
- import {TokenAmount, toTokenAmount} from "../../../../types/TokenAmount";
32
- import {BitcoinTokens, BtcToken, SCToken} from "../../../../types/Token";
33
- import {getLogger, LoggerType} from "../../../../utils/Logger";
34
- import {timeoutPromise} from "../../../../utils/TimeoutUtils";
35
- import {isLNURLWithdraw, LNURLWithdraw, LNURLWithdrawParamsWithUrl} from "../../../../types/lnurl/LNURLWithdraw";
36
- import {
37
- deserializePriceInfoType,
38
- isPriceInfoType,
39
- PriceInfoType,
40
- serializePriceInfoType
41
- } from "../../../../types/PriceInfoType";
42
- import {sha256} from "@noble/hashes/sha2";
43
- import {
44
- SwapExecutionActionSendToAddress,
45
- SwapExecutionActionSignSmartChainTx,
46
- SwapExecutionActionWait
47
- } from "../../../../types/SwapExecutionAction";
48
- import {
49
- SwapExecutionStepPayment,
50
- SwapExecutionStepSettlement
51
- } from "../../../../types/SwapExecutionStep";
52
- import {SwapStateInfo} from "../../../../types/SwapStateInfo";
53
-
54
- /**
55
- * State enum for FromBTCLNAuto swaps
56
- * @category Swaps/Lightning → Smart chain
57
- */
58
- export enum FromBTCLNAutoSwapState {
59
- /**
60
- * Swap has failed as the user didn't settle the HTLC on the destination before expiration
61
- */
62
- FAILED = -4,
63
- /**
64
- * Swap has expired for good and there is no way how it can be executed anymore
65
- */
66
- QUOTE_EXPIRED = -3,
67
- /**
68
- * A swap is almost expired, and it should be presented to the user as expired, though
69
- * there is still a chance that it will be processed
70
- */
71
- QUOTE_SOFT_EXPIRED = -2,
72
- /**
73
- * Swap HTLC on the destination chain has expired, it is not safe anymore to settle (claim) the
74
- * swap on the destination smart chain.
75
- */
76
- EXPIRED = -1,
77
- /**
78
- * Swap quote was created, use {@link FromBTCLNAutoSwap.getAddress} or {@link FromBTCLNAutoSwap.getHyperlink}
79
- * to get the bolt11 lightning network invoice to pay to initiate the swap, then use the
80
- * {@link FromBTCLNAutoSwap.waitForPayment} to wait till the lightning network payment is received
81
- * by the intermediary (LP) and the destination HTLC escrow is created
82
- */
83
- PR_CREATED = 0,
84
- /**
85
- * Lightning network payment has been received by the intermediary (LP), but the destination chain
86
- * HTLC escrow hasn't been created yet. Use {@link FromBTCLNAutoSwap.waitForPayment} to continue waiting
87
- * till the destination HTLC escrow is created.
88
- */
89
- PR_PAID = 1,
90
- /**
91
- * Swap escrow HTLC has been created on the destination chain, wait for automatic settlement by the watchtowers
92
- * using the {@link FromBTCLNAutoSwap.waitTillClaimed} function or settle manually using the
93
- * {@link FromBTCLNAutoSwap.claim} or {@link FromBTCLNAutoSwap.txsClaim} function.
94
- */
95
- CLAIM_COMMITED = 2,
96
- /**
97
- * Swap successfully settled and funds received on the destination chain
98
- */
99
- CLAIM_CLAIMED = 3
100
- }
101
-
102
- const FromBTCLNAutoSwapStateDescription = {
103
- [FromBTCLNAutoSwapState.FAILED]: "Swap has failed as the user didn't settle the HTLC on the destination before expiration",
104
- [FromBTCLNAutoSwapState.QUOTE_EXPIRED]: "Swap has expired for good and there is no way how it can be executed anymore",
105
- [FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED]: "A swap is expired, though there is still a chance that it will be processed",
106
- [FromBTCLNAutoSwapState.EXPIRED]: "Swap HTLC on the destination chain has expired, it is not safe anymore to settle (claim) the swap on the destination smart chain.",
107
- [FromBTCLNAutoSwapState.PR_CREATED]: "Swap quote was created, pay the bolt11 lightning network invoice to initiate the swap, then wait till the lightning network payment is received by the intermediary (LP) and the destination HTLC escrow is created",
108
- [FromBTCLNAutoSwapState.PR_PAID]: "Lightning network payment has been received by the intermediary (LP), but the destination chain HTLC escrow hasn't been created yet. Continue waiting till the destination HTLC escrow is created.",
109
- [FromBTCLNAutoSwapState.CLAIM_COMMITED]: "Swap escrow HTLC has been created on the destination chain, wait for automatic settlement by the watchtowers or settle manually.",
110
- [FromBTCLNAutoSwapState.CLAIM_CLAIMED]: "Swap successfully settled and funds received on the destination chain"
111
- };
112
-
113
- export type FromBTCLNAutoSwapInit<T extends SwapData> = IEscrowSwapInit<T> & {
114
- pr?: string,
115
- secret?: string,
116
- initialSwapData: T,
117
-
118
- btcAmountSwap?: bigint,
119
- btcAmountGas?: bigint,
120
-
121
- gasSwapFeeBtc: bigint,
122
- gasSwapFee: bigint,
123
- gasPricingInfo?: PriceInfoType,
124
-
125
- lnurl?: string,
126
- lnurlK1?: string,
127
- lnurlCallback?: string
128
- };
129
-
130
- export function isFromBTCLNAutoSwapInit<T extends SwapData>(obj: any): obj is FromBTCLNAutoSwapInit<T> {
131
- return (obj.pr==null || typeof obj.pr==="string") &&
132
- (obj.secret==null || typeof obj.secret==="string") &&
133
- (obj.btcAmountSwap==null || typeof obj.btcAmountSwap==="bigint") &&
134
- (obj.btcAmountGas==null || typeof obj.btcAmountGas==="bigint") &&
135
- typeof obj.gasSwapFeeBtc==="bigint" &&
136
- typeof obj.gasSwapFee==="bigint" &&
137
- (obj.gasPricingInfo==null || isPriceInfoType(obj.gasPricingInfo)) &&
138
- (obj.lnurl==null || typeof(obj.lnurl)==="string") &&
139
- (obj.lnurlK1==null || typeof(obj.lnurlK1)==="string") &&
140
- (obj.lnurlCallback==null || typeof(obj.lnurlCallback)==="string") &&
141
- isIEscrowSwapInit(obj);
142
- }
143
-
144
- /**
145
- * New escrow based (HTLC) swaps for Bitcoin Lightning -> Smart chain swaps not requiring manual settlement on
146
- * the destination by the user, and instead letting the LP initiate the escrow. Permissionless watchtower network
147
- * handles the claiming of HTLC, with the swap secret broadcasted over Nostr. Also adds a possibility for the user
148
- * to receive a native token on the destination chain as part of the swap (a "gas drop" feature).
149
- *
150
- * @category Swaps/Lightning → Smart chain
151
- */
152
- export class FromBTCLNAutoSwap<T extends ChainType = ChainType>
153
- extends IEscrowSwap<T, FromBTCLNAutoDefinition<T>>
154
- implements IAddressSwap, ISwapWithGasDrop<T>, IClaimableSwap<T, FromBTCLNAutoDefinition<T>, FromBTCLNAutoSwapState> {
155
-
156
- protected readonly TYPE: SwapType.FROM_BTCLN_AUTO = SwapType.FROM_BTCLN_AUTO;
157
- /**
158
- * @internal
159
- */
160
- protected readonly swapStateName = (state: number) => FromBTCLNAutoSwapState[state];
161
- /**
162
- * @internal
163
- */
164
- protected readonly swapStateDescription = FromBTCLNAutoSwapStateDescription;
165
- /**
166
- * @internal
167
- */
168
- protected readonly logger: LoggerType;
169
- /**
170
- * @internal
171
- */
172
- protected readonly inputToken: BtcToken<true> = BitcoinTokens.BTCLN;
173
-
174
- /**
175
- * Timestamp at which the HTLC was commited on the smart chain side
176
- * @internal
177
- */
178
- _commitedAt?: number;
179
-
180
- private readonly lnurlFailSignal: AbortController = new AbortController();
181
- private readonly usesClaimHashAsId: boolean;
182
- private readonly initialSwapData: T["Data"];
183
-
184
- private readonly btcAmountSwap?: bigint;
185
- private readonly btcAmountGas?: bigint;
186
-
187
- private readonly gasSwapFeeBtc: bigint;
188
- private readonly gasSwapFee: bigint;
189
-
190
- private readonly gasPricingInfo?: PriceInfoType;
191
-
192
- /**
193
- * In case the swap is recovered from on-chain data, the pr saved here is just a payment hash,
194
- * as it is impossible to retrieve the actual lightning network invoice paid purely from on-chain
195
- * data
196
- * @private
197
- */
198
- private pr?: string;
199
- private secret?: string;
200
-
201
- private lnurl?: string;
202
- private lnurlK1?: string;
203
- private lnurlCallback?: string;
204
- private prPosted?: boolean = false;
205
-
206
- private broadcastTickCounter: number = 0
207
-
208
- /**
209
- * Sets the LNURL data for the swap
210
- *
211
- * @internal
212
- */
213
- _setLNURLData(lnurl: string, lnurlK1: string, lnurlCallback: string) {
214
- this.lnurl = lnurl;
215
- this.lnurlK1 = lnurlK1;
216
- this.lnurlCallback = lnurlCallback;
217
- }
218
-
219
- constructor(wrapper: FromBTCLNAutoWrapper<T>, init: FromBTCLNAutoSwapInit<T["Data"]>);
220
- constructor(wrapper: FromBTCLNAutoWrapper<T>, obj: any);
221
- constructor(
222
- wrapper: FromBTCLNAutoWrapper<T>,
223
- initOrObject: FromBTCLNAutoSwapInit<T["Data"]> | any
224
- ) {
225
- if(isFromBTCLNAutoSwapInit(initOrObject) && initOrObject.url!=null) initOrObject.url += "/frombtcln_auto";
226
- super(wrapper, initOrObject);
227
- if(isFromBTCLNAutoSwapInit(initOrObject)) {
228
- this._state = FromBTCLNAutoSwapState.PR_CREATED;
229
- this.pr = initOrObject.pr;
230
- this.secret = initOrObject.secret;
231
- this.initialSwapData = initOrObject.initialSwapData;
232
- this.btcAmountSwap = initOrObject.btcAmountSwap;
233
- this.btcAmountGas = initOrObject.btcAmountGas;
234
- this.gasSwapFeeBtc = initOrObject.gasSwapFeeBtc;
235
- this.gasSwapFee = initOrObject.gasSwapFee;
236
- this.gasPricingInfo = initOrObject.gasPricingInfo;
237
- this.lnurl = initOrObject.lnurl;
238
- this.lnurlK1 = initOrObject.lnurlK1;
239
- this.lnurlCallback = initOrObject.lnurlCallback;
240
- this.usesClaimHashAsId = true;
241
- } else {
242
- this.pr = initOrObject.pr;
243
- this.secret = initOrObject.secret;
244
-
245
- if(initOrObject.initialSwapData==null) {
246
- this.initialSwapData = this._data!;
247
- } else {
248
- this.initialSwapData = SwapData.deserialize<T["Data"]>(initOrObject.initialSwapData);
249
- }
250
-
251
- this.btcAmountSwap = toBigInt(initOrObject.btcAmountSwap);
252
- this.btcAmountGas = toBigInt(initOrObject.btcAmountGas);
253
- this.gasSwapFeeBtc = toBigInt(initOrObject.gasSwapFeeBtc);
254
- this.gasSwapFee = toBigInt(initOrObject.gasSwapFee);
255
- this.gasPricingInfo = deserializePriceInfoType(initOrObject.gasPricingInfo);
256
-
257
- this._commitTxId = initOrObject.commitTxId;
258
- this._claimTxId = initOrObject.claimTxId;
259
- this._commitedAt = initOrObject.commitedAt;
260
-
261
- this.lnurl = initOrObject.lnurl;
262
- this.lnurlK1 = initOrObject.lnurlK1;
263
- this.lnurlCallback = initOrObject.lnurlCallback;
264
- this.prPosted = initOrObject.prPosted;
265
- this.usesClaimHashAsId = initOrObject.usesClaimHashAsId ?? false;
266
- }
267
- this.tryRecomputeSwapPrice();
268
- this.logger = getLogger("FromBTCLNAuto("+this.getIdentifierHashString()+"): ");
269
- }
270
-
271
- /**
272
- * @inheritDoc
273
- * @internal
274
- */
275
- protected getSwapData(): T["Data"] {
276
- return this._data ?? this.initialSwapData;
277
- }
278
-
279
- /**
280
- * @inheritDoc
281
- * @internal
282
- */
283
- protected upgradeVersion() { /*NOOP*/ }
284
-
285
- /**
286
- * @inheritDoc
287
- * @internal
288
- */
289
- protected tryRecomputeSwapPrice() {
290
- if(this.pricingInfo==null || this.btcAmountSwap==null) return;
291
- if(this.pricingInfo.swapPriceUSatPerToken==null) {
292
- const priceUsdPerBtc = this.pricingInfo.realPriceUsdPerBitcoin;
293
- this.pricingInfo = this.wrapper._prices.recomputePriceInfoReceive(
294
- this.chainIdentifier,
295
- this.btcAmountSwap,
296
- this.pricingInfo.satsBaseFee,
297
- this.pricingInfo.feePPM,
298
- this.getOutputAmountWithoutFee(),
299
- this.getSwapData().getToken()
300
- );
301
- this.pricingInfo.realPriceUsdPerBitcoin = priceUsdPerBtc;
302
- }
303
- }
304
-
305
-
306
- //////////////////////////////
307
- //// Pricing
308
-
309
- /**
310
- * @inheritDoc
311
- */
312
- async refreshPriceData(): Promise<void> {
313
- if(this.pricingInfo==null || this.btcAmountSwap==null) return;
314
- const usdPricePerBtc = this.pricingInfo.realPriceUsdPerBitcoin;
315
- this.pricingInfo = await this.wrapper._prices.isValidAmountReceive(
316
- this.chainIdentifier,
317
- this.btcAmountSwap,
318
- this.pricingInfo.satsBaseFee,
319
- this.pricingInfo.feePPM,
320
- this.getOutputAmountWithoutFee(),
321
- this.getSwapData().getToken(),
322
- undefined,
323
- undefined,
324
- this.swapFeeBtc
325
- );
326
- this.pricingInfo.realPriceUsdPerBitcoin = usdPricePerBtc;
327
- }
328
-
329
-
330
- //////////////////////////////
331
- //// Getters & utils
332
-
333
- /**
334
- * @inheritDoc
335
- * @internal
336
- */
337
- _getEscrowHash(): string | null {
338
- //Use claim hash in case the data is not yet known
339
- return this._data == null ? this.initialSwapData?.getClaimHash() : this._data?.getEscrowHash();
340
- }
341
-
342
- /**
343
- * @inheritDoc
344
- * @internal
345
- */
346
- _getInitiator(): string {
347
- return this.getSwapData().getClaimer();
348
- }
349
-
350
- /**
351
- * @inheritDoc
352
- */
353
- getId(): string {
354
- return this.getIdentifierHashString();
355
- }
356
-
357
- /**
358
- * @inheritDoc
359
- */
360
- getOutputAddress(): string | null {
361
- return this._getInitiator();
362
- }
363
-
364
- /**
365
- * @inheritDoc
366
- */
367
- getOutputTxId(): string | null {
368
- return this._claimTxId ?? null;
369
- }
370
-
371
- /**
372
- * @inheritDoc
373
- */
374
- requiresAction(): boolean {
375
- return this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED;
376
- }
377
-
378
- /**
379
- * @inheritDoc
380
- * @internal
381
- */
382
- protected getIdentifierHashString(): string {
383
- const id: string = this.usesClaimHashAsId
384
- ? this.getClaimHash()
385
- : this.getPaymentHash()!.toString("hex");
386
- if(this._randomNonce==null) return id;
387
- return id + this._randomNonce;
388
- }
389
-
390
- /**
391
- * Returns the payment hash of the swap and lightning network invoice, or `null` if not known (i.e. if
392
- * the swap was recovered from on-chain data, the payment hash might not be known)
393
- *
394
- * @internal
395
- */
396
- protected getPaymentHash(): Buffer | null {
397
- if(this.pr==null) return null;
398
- if(this.pr.toLowerCase().startsWith("ln")) {
399
- const parsed = bolt11Decode(this.pr);
400
- if(parsed.tagsObject.payment_hash==null) throw new Error("Swap invoice has no payment hash field!");
401
- return Buffer.from(parsed.tagsObject.payment_hash, "hex");
402
- }
403
- return Buffer.from(this.pr, "hex");
404
- }
405
-
406
- /**
407
- * @inheritDoc
408
- */
409
- getInputAddress(): string | null {
410
- return this.lnurl ?? this.pr ?? null;
411
- }
412
-
413
- /**
414
- * @inheritDoc
415
- */
416
- getInputTxId(): string | null {
417
- const paymentHash = this.getPaymentHash();
418
- if(paymentHash==null) return null;
419
- return paymentHash.toString("hex");
420
- }
421
-
422
- /**
423
- * Returns the lightning network BOLT11 invoice that needs to be paid as an input to the swap
424
- */
425
- getAddress(): string {
426
- return this.pr ?? "";
427
- }
428
-
429
- /**
430
- * @inheritDoc
431
- */
432
- getHyperlink(): string {
433
- return this.pr==null ? "" : "lightning:"+this.pr.toUpperCase();
434
- }
435
-
436
- /**
437
- * Returns the timeout time (in UNIX milliseconds) when the swap will definitelly be considered as expired
438
- * if the LP doesn't make it expired sooner
439
- */
440
- getDefinitiveExpiryTime(): number {
441
- if(this.pr==null || !this.pr.toLowerCase().startsWith("ln")) return 0;
442
- const decoded = bolt11Decode(this.pr);
443
- if(decoded.tagsObject.min_final_cltv_expiry==null) throw new Error("Swap invoice doesn't contain final ctlv delta field!");
444
- if(decoded.timeExpireDate==null) throw new Error("Swap invoice doesn't contain expiry date field!");
445
- const finalCltvExpiryDelta = decoded.tagsObject.min_final_cltv_expiry ?? 144;
446
- const finalCltvExpiryDelay = finalCltvExpiryDelta * this.wrapper._options.bitcoinBlocktime * this.wrapper._options.safetyFactor;
447
- return (decoded.timeExpireDate + finalCltvExpiryDelay)*1000;
448
- }
449
-
450
- /**
451
- * Returns timeout time (in UNIX milliseconds) when the swap htlc will expire
452
- */
453
- getHtlcTimeoutTime(): number | null {
454
- return this._data==null ? null : Number(this.wrapper._getHtlcTimeout(this._data))*1000;
455
- }
456
-
457
- /**
458
- * @inheritDoc
459
- */
460
- isFinished(): boolean {
461
- return this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED || this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED || this._state===FromBTCLNAutoSwapState.FAILED;
462
- }
463
-
464
- /**
465
- * @inheritDoc
466
- */
467
- isClaimable(): boolean {
468
- return this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED;
469
- }
470
-
471
- /**
472
- * @inheritDoc
473
- */
474
- isSuccessful(): boolean {
475
- return this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED;
476
- }
477
-
478
- /**
479
- * @inheritDoc
480
- */
481
- isFailed(): boolean {
482
- return this._state===FromBTCLNAutoSwapState.FAILED || this._state===FromBTCLNAutoSwapState.EXPIRED;
483
- }
484
-
485
- /**
486
- * @inheritDoc
487
- */
488
- isInProgress(): boolean {
489
- return (this._state===FromBTCLNAutoSwapState.PR_CREATED && this.initiated) ||
490
- (this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED && this.initiated) ||
491
- this._state===FromBTCLNAutoSwapState.PR_PAID ||
492
- this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED;
493
- }
494
-
495
- /**
496
- * @inheritDoc
497
- */
498
- isQuoteExpired(): boolean {
499
- return this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED;
500
- }
501
-
502
- /**
503
- * @inheritDoc
504
- */
505
- isQuoteSoftExpired(): boolean {
506
- return this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED;
507
- }
508
-
509
- /**
510
- * @inheritDoc
511
- */
512
- _verifyQuoteDefinitelyExpired(): Promise<boolean> {
513
- return Promise.resolve(this.getDefinitiveExpiryTime()<Date.now());
514
- }
515
-
516
- /**
517
- * @inheritDoc
518
- */
519
- _verifyQuoteValid(): Promise<boolean> {
520
- return Promise.resolve(this.getQuoteExpiry()>Date.now());
521
- }
522
-
523
-
524
- //////////////////////////////
525
- //// Amounts & fees
526
-
527
- /**
528
- * Returns the satoshi amount of the lightning network invoice, or `null` if the lightning network
529
- * invoice is not known (i.e. when the swap was recovered from on-chain data, the paid invoice
530
- * cannot be recovered because it is purely off-chain)
531
- *
532
- * @internal
533
- */
534
- protected getLightningInvoiceSats(): bigint | null {
535
- if(this.pr==null || !this.pr.toLowerCase().startsWith("ln")) return null;
536
-
537
- const parsed = bolt11Decode(this.pr);
538
- if(parsed.millisatoshis==null) throw new Error("Swap invoice doesn't contain msat amount field!");
539
- return (BigInt(parsed.millisatoshis) + 999n) / 1000n;
540
- }
541
-
542
- /**
543
- * Returns the watchtower fee paid in BTC satoshis, or null if known (i.e. if the swap was recovered from
544
- * on-chain data)
545
- *
546
- * @protected
547
- */
548
- protected getWatchtowerFeeAmountBtc(): bigint | null {
549
- if(this.btcAmountGas==null) return null;
550
- return (this.btcAmountGas - this.gasSwapFeeBtc) * this.getSwapData().getClaimerBounty() / this.getSwapData().getTotalDeposit();
551
- }
552
-
553
- /**
554
- * Returns the input amount for the actual swap (excluding the input amount used to cover the "gas drop"
555
- * part of the swap), excluding fees
556
- *
557
- * @internal
558
- */
559
- protected getInputSwapAmountWithoutFee(): bigint | null {
560
- if(this.btcAmountSwap==null) return null;
561
- return this.btcAmountSwap - this.swapFeeBtc;
562
- }
563
-
564
- /**
565
- * Returns the input amount purely for the "gas drop" part of the swap (this much BTC in sats will be
566
- * swapped into the native gas token on the destination chain), excluding fees
567
- *
568
- * @internal
569
- */
570
- protected getInputGasAmountWithoutFee(): bigint | null {
571
- if(this.btcAmountGas==null) return null;
572
- return this.btcAmountGas - this.gasSwapFeeBtc;
573
- }
574
-
575
- /**
576
- * Get total btc amount in sats on the input, excluding the swap fee and watchtower fee
577
- *
578
- * @internal
579
- */
580
- protected getInputAmountWithoutFee(): bigint | null {
581
- if(this.btcAmountGas==null || this.btcAmountSwap==null) return null;
582
- return this.getInputSwapAmountWithoutFee()! + this.getInputGasAmountWithoutFee()! - this.getWatchtowerFeeAmountBtc()!;
583
- }
584
-
585
- /**
586
- * Returns the "would be" output amount if the swap charged no swap fee
587
- *
588
- * @internal
589
- */
590
- protected getOutputAmountWithoutFee(): bigint {
591
- return this.getSwapData().getAmount() + this.swapFee;
592
- }
593
-
594
- /**
595
- * @inheritDoc
596
- */
597
- getInputToken(): BtcToken<true> {
598
- return BitcoinTokens.BTCLN;
599
- }
600
-
601
- /**
602
- * @inheritDoc
603
- */
604
- getInput(): TokenAmount<BtcToken<true>> {
605
- return toTokenAmount(this.getLightningInvoiceSats(), this.inputToken, this.wrapper._prices, this.pricingInfo);
606
- }
607
-
608
- /**
609
- * @inheritDoc
610
- */
611
- getInputWithoutFee(): TokenAmount<BtcToken<true>> {
612
- return toTokenAmount(this.getInputAmountWithoutFee(), this.inputToken, this.wrapper._prices, this.pricingInfo);
613
- }
614
-
615
- /**
616
- * @inheritDoc
617
- */
618
- getOutputToken(): SCToken<T["ChainId"]> {
619
- return this.wrapper._tokens[this.getSwapData().getToken()];
620
- }
621
-
622
- /**
623
- * @inheritDoc
624
- */
625
- getOutput(): TokenAmount<SCToken<T["ChainId"]>, true> {
626
- return toTokenAmount(this.getSwapData().getAmount(), this.wrapper._tokens[this.getSwapData().getToken()], this.wrapper._prices, this.pricingInfo);
627
- }
628
-
629
- /**
630
- * @inheritDoc
631
- */
632
- getGasDropOutput(): TokenAmount<SCToken<T["ChainId"]>, true> {
633
- return toTokenAmount(
634
- this.getSwapData().getSecurityDeposit() - this.getSwapData().getClaimerBounty(),
635
- this.wrapper._tokens[this.getSwapData().getDepositToken()], this.wrapper._prices, this.gasPricingInfo
636
- );
637
- }
638
-
639
- /**
640
- * Returns the swap fee charged by the intermediary (LP) on this swap
641
- *
642
- * @internal
643
- */
644
- protected getSwapFee(): Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>> {
645
- if(this.pricingInfo==null) throw new Error("No pricing info known, cannot estimate fee!");
646
-
647
- const outputToken = this.wrapper._tokens[this.getSwapData().getToken()];
648
- const gasSwapFeeInOutputToken = this.gasSwapFeeBtc
649
- * (10n ** BigInt(outputToken.decimals))
650
- * 1_000_000n
651
- / this.pricingInfo.swapPriceUSatPerToken;
652
-
653
- const feeWithoutBaseFee = this.gasSwapFeeBtc + this.swapFeeBtc - this.pricingInfo.satsBaseFee;
654
- const inputSats = this.getLightningInvoiceSats();
655
- const swapFeePPM = inputSats!=null
656
- ? feeWithoutBaseFee * 1000000n / (inputSats - this.swapFeeBtc - this.gasSwapFeeBtc)
657
- : 0n;
658
-
659
- const amountInSrcToken = toTokenAmount(this.swapFeeBtc + this.gasSwapFeeBtc, BitcoinTokens.BTCLN, this.wrapper._prices, this.pricingInfo);
660
- return {
661
- amountInSrcToken,
662
- amountInDstToken: toTokenAmount(this.swapFee + gasSwapFeeInOutputToken, outputToken, this.wrapper._prices, this.pricingInfo),
663
- currentUsdValue: amountInSrcToken.currentUsdValue,
664
- pastUsdValue: amountInSrcToken.pastUsdValue,
665
- usdValue: amountInSrcToken.usdValue,
666
- composition: {
667
- base: toTokenAmount(this.pricingInfo.satsBaseFee, BitcoinTokens.BTCLN, this.wrapper._prices, this.pricingInfo),
668
- percentage: ppmToPercentage(swapFeePPM)
669
- }
670
- };
671
- }
672
-
673
- /**
674
- * Returns the fee to be paid to watchtowers on the destination chain to automatically
675
- * process and settle this swap without requiring any user interaction
676
- *
677
- * @internal
678
- */
679
- protected getWatchtowerFee(): Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>> {
680
- if(this.pricingInfo==null) throw new Error("No pricing info known, cannot estimate fee!");
681
-
682
- const btcWatchtowerFee = this.getWatchtowerFeeAmountBtc();
683
- const outputToken = this.wrapper._tokens[this.getSwapData().getToken()];
684
- const watchtowerFeeInOutputToken = btcWatchtowerFee==null ? 0n : btcWatchtowerFee
685
- * (10n ** BigInt(outputToken.decimals))
686
- * 1_000_000n
687
- / this.pricingInfo.swapPriceUSatPerToken;
688
-
689
- const amountInSrcToken = toTokenAmount(btcWatchtowerFee, BitcoinTokens.BTCLN, this.wrapper._prices, this.pricingInfo);
690
- return {
691
- amountInSrcToken,
692
- amountInDstToken: toTokenAmount(watchtowerFeeInOutputToken, outputToken, this.wrapper._prices, this.pricingInfo),
693
- currentUsdValue: amountInSrcToken.currentUsdValue,
694
- usdValue: amountInSrcToken.usdValue,
695
- pastUsdValue: amountInSrcToken.pastUsdValue
696
- };
697
- }
698
-
699
- /**
700
- * @inheritDoc
701
- */
702
- getFee(): Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>> {
703
- const swapFee = this.getSwapFee();
704
- const watchtowerFee = this.getWatchtowerFee();
705
-
706
- const amountInSrcToken = toTokenAmount(
707
- swapFee.amountInSrcToken.rawAmount + watchtowerFee.amountInSrcToken.rawAmount,
708
- BitcoinTokens.BTCLN, this.wrapper._prices, this.pricingInfo
709
- );
710
- return {
711
- amountInSrcToken,
712
- amountInDstToken: toTokenAmount(
713
- swapFee.amountInDstToken.rawAmount + watchtowerFee.amountInDstToken.rawAmount,
714
- this.wrapper._tokens[this.getSwapData().getToken()], this.wrapper._prices, this.pricingInfo
715
- ),
716
- currentUsdValue: amountInSrcToken.currentUsdValue,
717
- usdValue: amountInSrcToken.usdValue,
718
- pastUsdValue: amountInSrcToken.pastUsdValue
719
- };
720
- }
721
-
722
- /**
723
- * @inheritDoc
724
- */
725
- getFeeBreakdown(): [
726
- {type: FeeType.SWAP, fee: Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>>},
727
- {type: FeeType.NETWORK_OUTPUT, fee: Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>>}
728
- ] {
729
- return [
730
- {
731
- type: FeeType.SWAP,
732
- fee: this.getSwapFee()
733
- },
734
- {
735
- type: FeeType.NETWORK_OUTPUT,
736
- fee: this.getWatchtowerFee()
737
- }
738
- ];
739
- }
740
-
741
- private isValidSecretPreimage(secret: string) {
742
- const paymentHash = Buffer.from(sha256(Buffer.from(secret, "hex")));
743
- const claimHash = this._contract.getHashForHtlc(paymentHash).toString("hex");
744
- return this.getSwapData().getClaimHash()===claimHash;
745
- }
746
-
747
- /**
748
- * Sets the secret preimage for the swap, in case it is not known already
749
- *
750
- * @param secret Secret preimage that matches the expected payment hash
751
- *
752
- * @throws {Error} If an invalid secret preimage is provided
753
- */
754
- async setSecretPreimage(secret: string) {
755
- if(!this.isValidSecretPreimage(secret)) throw new Error("Invalid secret preimage provided, hash doesn't match!");
756
- this.secret = secret;
757
- await this._broadcastSecret().catch(e => this.logger.error("setSecretPreimage(): Failed to broadcast swap secret: ", e));
758
- }
759
-
760
- /**
761
- * Returns whether the secret preimage for this swap is known
762
- */
763
- hasSecretPreimage(): boolean {
764
- return this.secret != null;
765
- }
766
-
767
-
768
- //////////////////////////////
769
- //// Execution
770
-
771
- /**
772
- * Executes the swap with the provided bitcoin lightning network wallet or LNURL
773
- *
774
- * @param walletOrLnurlWithdraw Bitcoin lightning wallet to use to pay the lightning network invoice, or an LNURL-withdraw
775
- * link, wallet is not required and the LN invoice can be paid externally as well (just pass null or undefined here)
776
- * @param callbacks Callbacks to track the progress of the swap
777
- * @param options Optional options for the swap like AbortSignal, and timeouts/intervals
778
- * @param options.secret A swap secret to broadcast to watchtowers, generally only needed if the swap
779
- * was recovered from on-chain data, or the pre-image was generated outside the SDK
780
- *
781
- * @returns {boolean} Whether a swap was settled automatically by swap watchtowers or requires manual claim by the
782
- * user, in case `false` is returned the user should call `swap.claim()` to settle the swap on the destination manually
783
- */
784
- async execute(
785
- walletOrLnurlWithdraw?: MinimalLightningNetworkWalletInterface | LNURLWithdraw | string | null | undefined,
786
- callbacks?: {
787
- onSourceTransactionReceived?: (sourceTxId: string) => void,
788
- onSwapSettled?: (destinationTxId: string) => void
789
- },
790
- options?: {
791
- abortSignal?: AbortSignal,
792
- lightningTxCheckIntervalSeconds?: number,
793
- maxWaitTillAutomaticSettlementSeconds?: number,
794
- secret?: string
795
- }
796
- ): Promise<boolean> {
797
- if(this._state===FromBTCLNAutoSwapState.FAILED) throw new Error("Swap failed!");
798
- if(this._state===FromBTCLNAutoSwapState.EXPIRED) throw new Error("Swap HTLC expired!");
799
- if(this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) throw new Error("Swap quote expired!");
800
- if(this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED) throw new Error("Swap already settled!");
801
-
802
- let abortSignal = options?.abortSignal;
803
-
804
- if(this._state===FromBTCLNAutoSwapState.PR_CREATED) {
805
- if(walletOrLnurlWithdraw!=null && this.lnurl==null) {
806
- if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
807
- throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
808
-
809
- if(typeof(walletOrLnurlWithdraw)==="string" || isLNURLWithdraw(walletOrLnurlWithdraw)) {
810
- await this.settleWithLNURLWithdraw(walletOrLnurlWithdraw);
811
- } else {
812
- const paymentPromise = walletOrLnurlWithdraw.payInvoice(this.pr);
813
-
814
- const abortController = new AbortController();
815
- paymentPromise.catch(e => abortController.abort(e));
816
- if(options?.abortSignal!=null) options.abortSignal.addEventListener("abort", () => abortController.abort(options?.abortSignal?.reason));
817
- abortSignal = abortController.signal;
818
- }
819
- }
820
- }
821
-
822
- if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.PR_PAID) {
823
- const paymentSuccess = await this.waitForPayment(callbacks?.onSourceTransactionReceived, options?.lightningTxCheckIntervalSeconds, abortSignal);
824
- if (!paymentSuccess) throw new Error("Failed to receive lightning network payment");
825
- }
826
-
827
- if((this._state as FromBTCLNAutoSwapState)===FromBTCLNAutoSwapState.CLAIM_CLAIMED) return true;
828
-
829
- if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED) {
830
- if(this.secret==null && options?.secret==null)
831
- throw new Error("Tried to wait till settlement, but no secret pre-image is known, please pass the secret pre-image as an argument!");
832
- const success = await this.waitTillClaimed(options?.maxWaitTillAutomaticSettlementSeconds ?? 60, options?.abortSignal, options?.secret);
833
- if (success && callbacks?.onSwapSettled != null) callbacks.onSwapSettled(this.getOutputTxId()!);
834
- return success;
835
- }
836
-
837
- throw new Error("Invalid state reached!");
838
- }
839
-
840
- /**
841
- * @internal
842
- */
843
- protected async _getExecutionStatus(options?: {
844
- maxWaitTillAutomaticSettlementSeconds?: number,
845
- secret?: string
846
- }) {
847
- if(options?.secret!=null) await this.setSecretPreimage(options.secret);
848
-
849
- const state = this._state;
850
- const now = Date.now();
851
-
852
- let lightningPaymentStatus: SwapExecutionStepPayment<"LIGHTNING">["status"] = "inactive";
853
- let destinationSettlementStatus: SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">["status"] = "inactive";
854
- let buildCurrentAction: (actionOptions?: {
855
- manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"]
856
- }) => Promise<
857
- SwapExecutionActionSendToAddress<true> |
858
- SwapExecutionActionWait<"LP" | "SETTLEMENT"> |
859
- SwapExecutionActionSignSmartChainTx<T> |
860
- undefined
861
- > = async () => undefined;
862
-
863
- switch(state) {
864
- case FromBTCLNAutoSwapState.PR_CREATED: {
865
- const quoteValid = await this._verifyQuoteValid();
866
- lightningPaymentStatus = quoteValid ? "awaiting" : "soft_expired";
867
- if(quoteValid && this.pr!=null && this.pr.toLowerCase().startsWith("ln")) {
868
- buildCurrentAction = this._buildLightningPaymentAction.bind(this);
869
- }
870
- break;
871
- }
872
- case FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED:
873
- lightningPaymentStatus = "soft_expired";
874
- break;
875
- case FromBTCLNAutoSwapState.QUOTE_EXPIRED:
876
- lightningPaymentStatus = "expired";
877
- break;
878
- case FromBTCLNAutoSwapState.PR_PAID:
879
- lightningPaymentStatus = "received";
880
- destinationSettlementStatus = "waiting_lp";
881
- buildCurrentAction = this._buildWaitLpAction.bind(this);
882
- break;
883
- case FromBTCLNAutoSwapState.CLAIM_COMMITED:
884
- lightningPaymentStatus = "received";
885
- if(
886
- this._commitedAt==null ||
887
- options?.maxWaitTillAutomaticSettlementSeconds===0 ||
888
- (now - this._commitedAt) > (options?.maxWaitTillAutomaticSettlementSeconds ?? 60)*1000
889
- ) {
890
- destinationSettlementStatus = "awaiting_manual";
891
- if(this.hasSecretPreimage()) {
892
- //TODO: Maybe add an action that would prompt the user to reveal the pre-image
893
- buildCurrentAction = this._buildClaimSmartChainTxAction.bind(this);
894
- }
895
- } else {
896
- destinationSettlementStatus = "awaiting_automatic";
897
- //TODO: Maybe add an action that would prompt the user to reveal the pre-image
898
- buildCurrentAction = this._buildWaitSettlementAction.bind(this, options?.maxWaitTillAutomaticSettlementSeconds);
899
- }
900
- break;
901
- case FromBTCLNAutoSwapState.CLAIM_CLAIMED:
902
- lightningPaymentStatus = "confirmed";
903
- destinationSettlementStatus = "settled";
904
- break;
905
- case FromBTCLNAutoSwapState.EXPIRED:
906
- case FromBTCLNAutoSwapState.FAILED:
907
- lightningPaymentStatus = "expired";
908
- destinationSettlementStatus = "expired";
909
- break;
910
- }
911
-
912
- return {
913
- steps: [
914
- {
915
- type: "Payment",
916
- side: "source",
917
- chain: "LIGHTNING",
918
- title: "Lightning payment",
919
- description: "Pay the Lightning network invoice to initiate the swap",
920
- status: lightningPaymentStatus,
921
- initTxId: this.getInputTxId(),
922
- settleTxId: lightningPaymentStatus==="confirmed" ? this.getInputTxId() : undefined
923
- },
924
- {
925
- type: "Settlement",
926
- side: "destination",
927
- chain: this.chainIdentifier,
928
- title: "Destination settlement",
929
- description: `Wait for the LP to initiate on the ${this.chainIdentifier} side, then wait for automatic settlement, or settle manually if it takes too long`,
930
- status: destinationSettlementStatus,
931
- initTxId: this._commitTxId,
932
- settleTxId: this._claimTxId
933
- }
934
- ] as [
935
- SwapExecutionStepPayment<"LIGHTNING">,
936
- SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
937
- ],
938
- buildCurrentAction,
939
- state
940
- };
941
- }
942
-
943
- /**
944
- * @internal
945
- */
946
- private async _buildLightningPaymentAction(): Promise<SwapExecutionActionSendToAddress<true>> {
947
- return {
948
- type: "SendToAddress",
949
- name: "Deposit on Lightning",
950
- description: "Pay the lightning network invoice to initiate the swap",
951
- chain: "LIGHTNING",
952
- txs: [{
953
- type: "BOLT11_PAYMENT_REQUEST",
954
- address: this.getAddress(),
955
- hyperlink: this.getHyperlink(),
956
- amount: this.getInput()
957
- }],
958
- waitForTransactions: async (
959
- maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal
960
- ) => {
961
- const abortController = extendAbortController(
962
- abortSignal, maxWaitTimeSeconds, "Timed out waiting for lightning payment"
963
- );
964
- const success = await this._waitForLpPaymentReceived(pollIntervalSeconds, abortController.signal);
965
- if(!success) throw new Error("Quote expired while waiting for Lightning payment");
966
-
967
- return this.getInputTxId()!;
968
- }
969
- } as SwapExecutionActionSendToAddress<true>;
970
- }
971
-
972
- /**
973
- * @internal
974
- */
975
- private async _buildWaitLpAction(): Promise<SwapExecutionActionWait<"LP">> {
976
- return {
977
- type: "Wait",
978
- name: "Awaiting LP payout",
979
- description: "Wait for the LP to create the swap HTLC on the destination smart chain",
980
- pollTimeSeconds: 5,
981
- expectedTimeSeconds: 10,
982
- wait: async (
983
- maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal
984
- ) => {
985
- const abortController = extendAbortController(
986
- abortSignal, maxWaitTimeSeconds, "Timed out waiting for LP payout"
987
- );
988
- await this.waitTillCommited(pollIntervalSeconds, abortController.signal);
989
- }
990
- } as SwapExecutionActionWait<"LP">;
991
- }
992
-
993
- /**
994
- * @internal
995
- */
996
- private async _buildWaitSettlementAction(
997
- maxWaitTillAutomaticSettlementSeconds?: number
998
- ): Promise<SwapExecutionActionWait<"SETTLEMENT">> {
999
- return {
1000
- type: "Wait",
1001
- name: "Automatic settlement",
1002
- description: "Wait for automatic settlement by the watchtower",
1003
- pollTimeSeconds: 5,
1004
- expectedTimeSeconds: 10,
1005
- wait: async (
1006
- maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal
1007
- ) => {
1008
- await this.waitTillClaimed(
1009
- maxWaitTimeSeconds ?? maxWaitTillAutomaticSettlementSeconds ?? 60,
1010
- abortSignal,
1011
- undefined,
1012
- pollIntervalSeconds
1013
- );
1014
- }
1015
- } as SwapExecutionActionWait<"SETTLEMENT">;
1016
- }
1017
-
1018
- /**
1019
- * @inheritDoc
1020
- * @internal
1021
- */
1022
- async _submitExecutionTransactions(
1023
- txs: (T["SignedTXType"] | string)[],
1024
- abortSignal?: AbortSignal,
1025
- requiredStates?: FromBTCLNAutoSwapState[],
1026
- idempotent?: boolean
1027
- ): Promise<string[]> {
1028
- const parsedTxs: T["SignedTXType"][] = [];
1029
- for(let tx of txs) {
1030
- parsedTxs.push(typeof(tx)==="string" ? await this.wrapper._chain.deserializeSignedTx(tx) : tx);
1031
- }
1032
-
1033
- if(idempotent) {
1034
- // Handle idempotent calls
1035
- if(this.wrapper._chain.getTxId!=null) {
1036
- const txIds = await Promise.all(parsedTxs.map(tx => this.wrapper._chain.getTxId!(tx)));
1037
- const foundTxId = txIds.find(txId => this._claimTxId===txId);
1038
- if(foundTxId!=null) return txIds;
1039
- }
1040
- }
1041
-
1042
- if(requiredStates!=null && !requiredStates.includes(this._state)) throw new Error("Swap state has changed before transactions were submitted!");
1043
-
1044
- if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED) {
1045
- const txIds = await this.wrapper._chain.sendSignedAndConfirm(parsedTxs, true, abortSignal, false);
1046
- await this.waitTillClaimed(undefined, abortSignal);
1047
- return txIds;
1048
- }
1049
-
1050
- throw new Error("Invalid swap state for transaction submission!");
1051
- }
1052
-
1053
- /**
1054
- * @internal
1055
- */
1056
- private async _buildClaimSmartChainTxAction(actionOptions?: {
1057
- manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
1058
- secret?: string
1059
- }): Promise<SwapExecutionActionSignSmartChainTx<T>> {
1060
- const signerAddress =
1061
- await this.wrapper._getSignerAddress(actionOptions?.manualSettlementSmartChainSigner);
1062
-
1063
- return {
1064
- type: "SignSmartChainTransaction",
1065
- name: "Settle manually",
1066
- description: "Manually settle (claim) the swap on the destination smart chain",
1067
- chain: this.chainIdentifier,
1068
- txs: await this.prepareTransactions(this.txsClaim(actionOptions?.manualSettlementSmartChainSigner, actionOptions?.secret)),
1069
- submitTransactions: async (txs: (T["SignedTXType"] | string)[], abortSignal?: AbortSignal, idempotent?: boolean) => {
1070
- return this._submitExecutionTransactions(
1071
- txs,
1072
- abortSignal,
1073
- [FromBTCLNAutoSwapState.CLAIM_COMMITED],
1074
- idempotent
1075
- );
1076
- },
1077
- requiredSigner: signerAddress ?? this._getInitiator()
1078
- } as SwapExecutionActionSignSmartChainTx<T>;
1079
- }
1080
-
1081
- /**
1082
- *
1083
- * @param options.manualSettlementSmartChainSigner Optional smart chain signer to create a manual claim (settlement) transaction
1084
- * @param options.maxWaitTillAutomaticSettlementSeconds Maximum time to wait for an automatic settlement after
1085
- * the bitcoin transaction is confirmed (defaults to 60 seconds)
1086
- * @param options.secret A swap secret to broadcast to watchtowers, generally only needed if the swap
1087
- * was recovered from on-chain data, or the pre-image was generated outside the SDK
1088
- */
1089
- async getExecutionAction(options?: {
1090
- manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
1091
- maxWaitTillAutomaticSettlementSeconds?: number,
1092
- secret?: string
1093
- }): Promise<
1094
- SwapExecutionActionSendToAddress<true> |
1095
- SwapExecutionActionWait<"LP" | "SETTLEMENT"> |
1096
- SwapExecutionActionSignSmartChainTx<T> |
1097
- undefined
1098
- > {
1099
- const executionStatus = await this._getExecutionStatus(options);
1100
- return executionStatus.buildCurrentAction(options);
1101
- }
1102
-
1103
- /**
1104
- * @inheritDoc
1105
- */
1106
- // TODO: Figure how we gonna trigger an LNURL-withdraw with the execution actions
1107
- async getExecutionStatus(options?: {
1108
- skipBuildingAction?: boolean,
1109
- manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
1110
- maxWaitTillAutomaticSettlementSeconds?: number,
1111
- secret?: string
1112
- }): Promise<{
1113
- steps: [
1114
- SwapExecutionStepPayment<"LIGHTNING">,
1115
- SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
1116
- ],
1117
- currentAction:
1118
- SwapExecutionActionSendToAddress<true> |
1119
- SwapExecutionActionWait<"LP" | "SETTLEMENT"> |
1120
- SwapExecutionActionSignSmartChainTx<T> |
1121
- undefined,
1122
- stateInfo: SwapStateInfo<FromBTCLNAutoSwapState>
1123
- }> {
1124
- const executionStatus = await this._getExecutionStatus(options);
1125
- return {
1126
- steps: executionStatus.steps,
1127
- currentAction: options?.skipBuildingAction ? undefined : await executionStatus.buildCurrentAction(options),
1128
- stateInfo: this._getStateInfo(executionStatus.state)
1129
- };
1130
- }
1131
-
1132
- /**
1133
- * @inheritDoc
1134
- */
1135
- async getExecutionSteps(options?: {
1136
- maxWaitTillAutomaticSettlementSeconds?: number
1137
- }): Promise<[
1138
- SwapExecutionStepPayment<"LIGHTNING">,
1139
- SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
1140
- ]> {
1141
- return (await this._getExecutionStatus(options)).steps;
1142
- }
1143
-
1144
-
1145
- //////////////////////////////
1146
- //// Payment
1147
-
1148
- /**
1149
- * Checks whether the LP received the LN payment
1150
- *
1151
- * @param save If the new swap state should be saved
1152
- *
1153
- * @internal
1154
- */
1155
- async _checkIntermediaryPaymentReceived(save: boolean = true): Promise<boolean | null> {
1156
- if(
1157
- this._state===FromBTCLNAutoSwapState.PR_PAID ||
1158
- this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED ||
1159
- this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED ||
1160
- this._state===FromBTCLNAutoSwapState.FAILED ||
1161
- this._state===FromBTCLNAutoSwapState.EXPIRED
1162
- ) return true;
1163
- if(this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED) return false;
1164
- if(this.url==null) return false;
1165
-
1166
- const paymentHash = this.getPaymentHash();
1167
- if(paymentHash==null)
1168
- throw new Error("Failed to check LP payment received, payment hash not known (probably recovered swap?)");
1169
-
1170
- const resp = await this.wrapper._lpApi.getInvoiceStatus(this.url, paymentHash.toString("hex"));
1171
- switch(resp.code) {
1172
- case InvoiceStatusResponseCodes.PAID:
1173
- const data = new (this.wrapper._swapDataDeserializer(this._contractVersion))(resp.data.data);
1174
- if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) try {
1175
- await this._saveRealSwapData(data, save);
1176
- return true;
1177
- } catch (e) {}
1178
- return null;
1179
- case InvoiceStatusResponseCodes.EXPIRED:
1180
- this._state = FromBTCLNAutoSwapState.QUOTE_EXPIRED;
1181
- this.initiated = true;
1182
- if(save) await this._saveAndEmit();
1183
- return false;
1184
- default:
1185
- return null;
1186
- }
1187
- }
1188
-
1189
- /**
1190
- * Checks and overrides the swap data for this swap. This is used to set the swap data from
1191
- * on-chain events.
1192
- *
1193
- * @param data Swap data of the escrow swap
1194
- * @param save If the new data should be saved
1195
- *
1196
- * @internal
1197
- */
1198
- async _saveRealSwapData(data: T["Data"], save?: boolean): Promise<boolean> {
1199
- await this.checkIntermediaryReturnedData(data);
1200
- if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) {
1201
- this._state = FromBTCLNAutoSwapState.PR_PAID;
1202
- this._data = data;
1203
- this.initiated = true;
1204
- if(save) await this._saveAndEmit();
1205
- return true;
1206
- }
1207
- return false;
1208
- }
1209
-
1210
- /**
1211
- * Waits till a lightning network payment is received by the intermediary, and the intermediary
1212
- * initiates the swap HTLC on the smart chain side. After the HTLC is initiated you can wait
1213
- * for an automatic settlement by the watchtowers with the {@link waitTillClaimed} function,
1214
- * or settle manually using the {@link claim} or {@link txsClaim} functions.
1215
- *
1216
- * If this swap is using an LNURL-withdraw link as input, it automatically posts the
1217
- * generated invoice to the LNURL service to pay it.
1218
- *
1219
- * @remarks For internal use, rather use {@link waitForPayment} which properly waits till the LP also
1220
- * offers a swap HTLC.
1221
- *
1222
- * @param abortSignal Abort signal to stop waiting for payment
1223
- * @param checkIntervalSeconds How often to poll the intermediary for answer (default 5 seconds)
1224
- *
1225
- * @internal
1226
- */
1227
- async _waitForLpPaymentReceived(checkIntervalSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
1228
- checkIntervalSeconds ??= 5;
1229
- if(this._state>=FromBTCLNAutoSwapState.PR_PAID) return true;
1230
- if(
1231
- this._state!==FromBTCLNAutoSwapState.PR_CREATED
1232
- ) throw new Error("Must be in PR_CREATED state!");
1233
-
1234
- const abortController = new AbortController();
1235
- if(abortSignal!=null) abortSignal.addEventListener("abort", () => abortController.abort(abortSignal.reason));
1236
-
1237
- let save = false;
1238
-
1239
- if(this.lnurl!=null && this.lnurlK1!=null && this.lnurlCallback!=null && !this.prPosted) {
1240
- if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
1241
- throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
1242
-
1243
- LNURL.postInvoiceToLNURLWithdraw({k1: this.lnurlK1, callback: this.lnurlCallback}, this.pr).catch(e => {
1244
- this.lnurlFailSignal.abort(e);
1245
- });
1246
- this.prPosted = true;
1247
- save ||= true;
1248
- }
1249
-
1250
- if(!this.initiated) {
1251
- this.initiated = true;
1252
- save ||= true;
1253
- }
1254
-
1255
- if(save) await this._saveAndEmit();
1256
-
1257
- let lnurlFailListener = () => abortController.abort(this.lnurlFailSignal.signal.reason);
1258
- this.lnurlFailSignal.signal.addEventListener("abort", lnurlFailListener);
1259
- this.lnurlFailSignal.signal.throwIfAborted();
1260
-
1261
- const paymentHash = this.getPaymentHash();
1262
- if(paymentHash==null)
1263
- throw new Error("Swap payment hash not available, the swap was probably recovered!");
1264
-
1265
- if(this.wrapper._messenger.warmup!=null) await this.wrapper._messenger.warmup().catch(e => {
1266
- this.logger.warn("waitForPayment(): Failed to warmup messenger: ", e);
1267
- });
1268
-
1269
- if(this._state===FromBTCLNAutoSwapState.PR_CREATED) {
1270
- const promises: Promise<boolean | undefined>[] = [
1271
- this.waitTillState(FromBTCLNAutoSwapState.PR_PAID, "gte", abortController.signal).then(() => true)
1272
- ];
1273
- if(this.url!=null) promises.push((async () => {
1274
- let resp: InvoiceStatusResponse = {code: InvoiceStatusResponseCodes.PENDING, msg: ""};
1275
- while(!abortController.signal.aborted && resp.code===InvoiceStatusResponseCodes.PENDING) {
1276
- resp = await this.wrapper._lpApi.getInvoiceStatus(this.url!, paymentHash.toString("hex"));
1277
- if(resp.code===InvoiceStatusResponseCodes.PENDING)
1278
- await timeoutPromise(checkIntervalSeconds*1000, abortController.signal);
1279
- }
1280
- this.lnurlFailSignal.signal.removeEventListener("abort", lnurlFailListener);
1281
- abortController.signal.throwIfAborted();
1282
-
1283
- if(resp.code===InvoiceStatusResponseCodes.PAID) {
1284
- const swapData = new (this.wrapper._swapDataDeserializer(this._contractVersion))(resp.data.data);
1285
- return await this._saveRealSwapData(swapData, true);
1286
- }
1287
-
1288
- if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) {
1289
- if(resp.code===InvoiceStatusResponseCodes.EXPIRED) {
1290
- await this._saveAndEmit(FromBTCLNAutoSwapState.QUOTE_EXPIRED);
1291
- }
1292
- return false;
1293
- }
1294
- })());
1295
- const paymentResult = await Promise.race(promises);
1296
- abortController.abort();
1297
-
1298
- if(!paymentResult) return false;
1299
- }
1300
-
1301
- return this._state>=FromBTCLNAutoSwapState.PR_PAID;
1302
- }
1303
-
1304
-
1305
- /**
1306
- * Checks the data returned by the intermediary in the payment auth request
1307
- *
1308
- * @param data Parsed swap data as returned by the intermediary
1309
- *
1310
- * @throws {IntermediaryError} If the returned are not valid
1311
- * @throws {Error} If the swap is already committed on-chain
1312
- *
1313
- * @private
1314
- */
1315
- private async checkIntermediaryReturnedData(data: T["Data"]): Promise<void> {
1316
- if (!data.isPayOut()) throw new IntermediaryError("Invalid not pay out");
1317
- if (data.getType() !== ChainSwapType.HTLC) throw new IntermediaryError("Invalid swap type");
1318
- if (!data.isOfferer(this.getSwapData().getOfferer())) throw new IntermediaryError("Invalid offerer used");
1319
- if (!data.isClaimer(this._getInitiator())) throw new IntermediaryError("Invalid claimer used");
1320
- if (!data.isToken(this.getSwapData().getToken())) throw new IntermediaryError("Invalid token used");
1321
- if (data.getSecurityDeposit() !== this.getSwapData().getSecurityDeposit()) throw new IntermediaryError("Invalid security deposit!");
1322
- if (data.getClaimerBounty() !== this.getSwapData().getClaimerBounty()) throw new IntermediaryError("Invalid security deposit!");
1323
- if (data.getAmount() < this.getSwapData().getAmount()) throw new IntermediaryError("Invalid amount received!");
1324
- if (data.getClaimHash() !== this.getSwapData().getClaimHash()) throw new IntermediaryError("Invalid payment hash used!");
1325
- if (!data.isDepositToken(this.getSwapData().getDepositToken())) throw new IntermediaryError("Invalid deposit token used!");
1326
- if (data.hasSuccessAction()) throw new IntermediaryError("Invalid has success action");
1327
-
1328
- if (await this.wrapper._contract(this._contractVersion).isExpired(this._getInitiator(), data)) throw new IntermediaryError("Not enough time to claim!");
1329
- if (this.wrapper._getHtlcTimeout(data) <= (Date.now()/1000)) throw new IntermediaryError("HTLC expires too soon!");
1330
- }
1331
-
1332
- /**
1333
- * Waits till a lightning network payment is received by the intermediary, and the intermediary
1334
- * initiates the swap HTLC on the smart chain side. After the HTLC is initiated you can wait
1335
- * for an automatic settlement by the watchtowers with the {@link waitTillClaimed} function,
1336
- * or settle manually using the {@link claim} or {@link txsClaim} functions.
1337
- *
1338
- * If this swap is using an LNURL-withdraw link as input, it automatically posts the
1339
- * generated invoice to the LNURL service to pay it.
1340
- *
1341
- * @param onPaymentReceived Callback as for when the LP reports having received the ln payment
1342
- * @param abortSignal Abort signal to stop waiting for payment
1343
- * @param checkIntervalSeconds How often to poll the intermediary for answer (default 5 seconds)
1344
- */
1345
- async waitForPayment(onPaymentReceived?: (txId: string) => void, checkIntervalSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
1346
- checkIntervalSeconds ??= 5;
1347
- if(this._state===FromBTCLNAutoSwapState.PR_PAID) {
1348
- await this.waitTillCommited(checkIntervalSeconds, abortSignal);
1349
- }
1350
- if(this._state>=FromBTCLNAutoSwapState.CLAIM_COMMITED) return true;
1351
-
1352
- const success = await this._waitForLpPaymentReceived(checkIntervalSeconds, abortSignal);
1353
- if(!success) return false;
1354
-
1355
- if(onPaymentReceived!=null) onPaymentReceived(this.getInputTxId()!);
1356
-
1357
- if((this._state as FromBTCLNAutoSwapState)===FromBTCLNAutoSwapState.PR_PAID) {
1358
- await this.waitTillCommited(checkIntervalSeconds, abortSignal);
1359
- }
1360
-
1361
- return this._state>=FromBTCLNAutoSwapState.CLAIM_COMMITED;
1362
- }
1363
-
1364
-
1365
- //////////////////////////////
1366
- //// Commit
1367
-
1368
- /**
1369
- * Waits till the intermediary (LP) initiates the swap HTLC escrow on the destination smart chain side
1370
- *
1371
- * @param checkIntervalSeconds How often to check via a polling watchdog
1372
- * @param abortSignal Abort signal
1373
- *
1374
- * @internal
1375
- */
1376
- protected async waitTillCommited(checkIntervalSeconds?: number, abortSignal?: AbortSignal): Promise<void> {
1377
- if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED || this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED) return Promise.resolve();
1378
- if(this._state!==FromBTCLNAutoSwapState.PR_PAID) throw new Error("Invalid state");
1379
-
1380
- const abortController = extendAbortController(abortSignal);
1381
- let result: SwapCommitState | number | null;
1382
- try {
1383
- result = await Promise.race([
1384
- this.watchdogWaitTillCommited(checkIntervalSeconds, abortController.signal),
1385
- this.waitTillState(FromBTCLNAutoSwapState.CLAIM_COMMITED, "gte", abortController.signal).then(() => 0)
1386
- ]);
1387
- abortController.abort();
1388
- } catch (e) {
1389
- abortController.abort();
1390
- throw e;
1391
- }
1392
-
1393
- if(result===null) {
1394
- this.logger.debug("waitTillCommited(): Resolved from watchdog - HTLC expired");
1395
- if(
1396
- this._state===FromBTCLNAutoSwapState.PR_PAID
1397
- ) {
1398
- await this._saveAndEmit(FromBTCLNAutoSwapState.EXPIRED);
1399
- }
1400
- return;
1401
- }
1402
-
1403
- if(
1404
- this._state===FromBTCLNAutoSwapState.PR_PAID
1405
- ) {
1406
- if(typeof(result)==="object" && (result as any).getInitTxId!=null && this._commitTxId==null)
1407
- this._commitTxId = await (result as any).getInitTxId();
1408
- this._commitedAt ??= Date.now();
1409
- await this._saveAndEmit(FromBTCLNAutoSwapState.CLAIM_COMMITED);
1410
- }
1411
-
1412
- if(result===0) {
1413
- this.logger.debug("waitTillCommited(): Resolved from state changed");
1414
- } else if(result!=null) {
1415
- this.logger.debug("waitTillCommited(): Resolved from watchdog - commited");
1416
- if(this.secret!=null) await this._broadcastSecret().catch(e => {
1417
- this.logger.error("waitTillCommited(): Error broadcasting swap secret: ", e);
1418
- });
1419
- }
1420
- }
1421
-
1422
-
1423
- //////////////////////////////
1424
- //// Claim
1425
-
1426
- /**
1427
- * @inheritDoc
1428
- *
1429
- * @param _signer Optional signer address to use for claiming the swap, can also be different from the initializer
1430
- * @param secret A swap secret to use for the claim transaction, generally only needed if the swap
1431
- * was recovered from on-chain data, or the pre-image was generated outside the SDK
1432
- *
1433
- * @throws {Error} If in invalid state (must be {@link FromBTCLNAutoSwapState.CLAIM_COMMITED})
1434
- */
1435
- async txsClaim(_signer?: string | T["Signer"] | T["NativeSigner"], secret?: string): Promise<T["TX"][]> {
1436
- let address: string | undefined = undefined;
1437
- if(_signer!=null) {
1438
- if (typeof (_signer) === "string") {
1439
- address = _signer;
1440
- } else if (isAbstractSigner(_signer)) {
1441
- address = _signer.getAddress();
1442
- } else {
1443
- address = (await this.wrapper._chain.wrapSigner(_signer)).getAddress();
1444
- }
1445
- }
1446
- if(this._state!==FromBTCLNAutoSwapState.CLAIM_COMMITED) throw new Error("Must be in CLAIM_COMMITED state!");
1447
- if(this._data==null) throw new Error("Unknown data, wrong state?");
1448
-
1449
- const useSecret = secret ?? this.secret;
1450
- if(useSecret==null)
1451
- throw new Error("Swap secret pre-image not known and not provided, please provide the swap secret pre-image as an argument");
1452
- if(!this.isValidSecretPreimage(useSecret))
1453
- throw new Error("Invalid swap secret pre-image provided!");
1454
-
1455
- return await this._contract.txsClaimWithSecret(
1456
- address ?? this._getInitiator(),
1457
- this._data, useSecret, true, true
1458
- );
1459
- }
1460
-
1461
- /**
1462
- * @inheritDoc
1463
- *
1464
- * @param _signer Signer to sign the transactions with, can also be different to the initializer
1465
- * @param abortSignal Abort signal to stop waiting for transaction confirmation
1466
- * @param onBeforeTxSent
1467
- * @param secret A swap secret to use for the claim transaction, generally only needed if the swap
1468
- * was recovered from on-chain data, or the pre-image was generated outside the SDK
1469
- */
1470
- async claim(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, onBeforeTxSent?: (txId: string) => void, secret?: string): Promise<string> {
1471
- const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
1472
- let txCount = 0;
1473
- const txs = await this.txsClaim(_signer, secret);
1474
- const result = await this.wrapper._chain.sendAndConfirm(
1475
- signer, txs, true, abortSignal, undefined, (txId: string) => {
1476
- txCount++;
1477
- if(onBeforeTxSent!=null && txCount===1) onBeforeTxSent(txId);
1478
- return Promise.resolve();
1479
- }
1480
- );
1481
-
1482
- this._claimTxId = result[0];
1483
- if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED || this._state===FromBTCLNAutoSwapState.EXPIRED || this._state===FromBTCLNAutoSwapState.FAILED) {
1484
- await this._saveAndEmit(FromBTCLNAutoSwapState.CLAIM_CLAIMED);
1485
- }
1486
- return result[0];
1487
- }
1488
-
1489
- /**
1490
- * Waits till the swap is successfully settled (claimed), should be called after sending the claim (settlement)
1491
- * transactions manually to wait till the SDK processes the settlement and updates the swap state accordingly.
1492
- *
1493
- * @param maxWaitTimeSeconds Maximum time in seconds to wait for the swap to be settled
1494
- * @param abortSignal AbortSignal
1495
- * @param secret A swap secret to broadcast to watchtowers, generally only needed if the swap
1496
- * was recovered from on-chain data, or the pre-image was generated outside the SDK
1497
- * @param pollIntervalSeconds How often to poll via the watchdog
1498
- *
1499
- * @throws {Error} If swap is in invalid state (must be {@link FromBTCLNAutoSwapState.CLAIM_COMMITED})
1500
- * @throws {Error} If the LP refunded sooner than we were able to claim
1501
- * @returns {boolean} whether the swap was claimed in time or not
1502
- */
1503
- async waitTillClaimed(maxWaitTimeSeconds?: number, abortSignal?: AbortSignal, secret?: string, pollIntervalSeconds?: number): Promise<boolean> {
1504
- if(this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED) return Promise.resolve(true);
1505
- if(this._state!==FromBTCLNAutoSwapState.CLAIM_COMMITED) throw new Error("Invalid state (not CLAIM_COMMITED)");
1506
-
1507
- if(secret!=null) {
1508
- if(!this.isValidSecretPreimage(secret))
1509
- throw new Error("Invalid swap secret pre-image provided!");
1510
- this.secret = secret;
1511
- }
1512
-
1513
- const abortController = new AbortController();
1514
- if(abortSignal!=null) abortSignal.addEventListener("abort", () => abortController.abort(abortSignal.reason));
1515
- let timedOut: boolean = false;
1516
- if(maxWaitTimeSeconds!=null) {
1517
- const timeout = setTimeout(() => {
1518
- timedOut = true;
1519
- abortController.abort();
1520
- }, maxWaitTimeSeconds * 1000);
1521
- abortController.signal.addEventListener("abort", () => clearTimeout(timeout));
1522
- }
1523
-
1524
- let res: 0 | 1 | SwapCommitState;
1525
- try {
1526
- res = await Promise.race([
1527
- this.watchdogWaitTillResult(pollIntervalSeconds, abortController.signal),
1528
- this.waitTillState(FromBTCLNAutoSwapState.CLAIM_CLAIMED, "eq", abortController.signal).then(() => 0 as const),
1529
- this.waitTillState(FromBTCLNAutoSwapState.EXPIRED, "eq", abortController.signal).then(() => 1 as const),
1530
- ]);
1531
- abortController.abort();
1532
- } catch (e) {
1533
- abortController.abort();
1534
- if(timedOut) return false;
1535
- throw e;
1536
- }
1537
-
1538
- if(res===0) {
1539
- this.logger.debug("waitTillClaimed(): Resolved from state change (CLAIM_CLAIMED)");
1540
- return true;
1541
- }
1542
- if(res===1) {
1543
- this.logger.debug("waitTillClaimed(): Resolved from state change (EXPIRED)");
1544
- throw new Error("Swap expired during claiming");
1545
- }
1546
- this.logger.debug("waitTillClaimed(): Resolved from watchdog");
1547
-
1548
- if(res?.type===SwapCommitStateType.PAID) {
1549
- if((this._state as FromBTCLNAutoSwapState)!==FromBTCLNAutoSwapState.CLAIM_CLAIMED) {
1550
- this._claimTxId = await res.getClaimTxId();
1551
- await this._saveAndEmit(FromBTCLNAutoSwapState.CLAIM_CLAIMED);
1552
- }
1553
- }
1554
- if(res?.type===SwapCommitStateType.NOT_COMMITED || res?.type===SwapCommitStateType.EXPIRED) {
1555
- if(
1556
- (this._state as FromBTCLNAutoSwapState)!==FromBTCLNAutoSwapState.CLAIM_CLAIMED &&
1557
- (this._state as FromBTCLNAutoSwapState)!==FromBTCLNAutoSwapState.FAILED
1558
- ) {
1559
- await this._saveAndEmit(FromBTCLNAutoSwapState.FAILED);
1560
- }
1561
- throw new Error("Swap expired during claiming");
1562
- }
1563
- return true;
1564
- }
1565
-
1566
-
1567
- //////////////////////////////
1568
- //// LNURL
1569
-
1570
- /**
1571
- * Whether this swap uses an LNURL-withdraw link
1572
- */
1573
- isLNURL(): boolean {
1574
- return this.lnurl!=null;
1575
- }
1576
-
1577
- /**
1578
- * Gets the used LNURL or `null` if this is not an LNURL-withdraw swap
1579
- */
1580
- getLNURL(): string | null {
1581
- return this.lnurl ?? null;
1582
- }
1583
-
1584
- /**
1585
- * Pay the generated lightning network invoice with an LNURL-withdraw link, this
1586
- * is useful when you want to display a lightning payment QR code and also want to
1587
- * allow payments using LNURL-withdraw NFC cards.
1588
- *
1589
- * Note that the swap needs to be created **without** an LNURL to begin with for this function
1590
- * to work. If this swap is already using an LNURL-withdraw link, this function throws.
1591
- */
1592
- async settleWithLNURLWithdraw(lnurl: string | LNURLWithdraw): Promise<void> {
1593
- if(
1594
- this._state!==FromBTCLNAutoSwapState.PR_CREATED &&
1595
- this._state!==FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED
1596
- ) throw new Error("Must be in PR_CREATED state!");
1597
-
1598
- if(this.lnurl!=null) throw new Error("Cannot settle LNURL-withdraw swap with different LNURL");
1599
- let lnurlParams: LNURLWithdrawParamsWithUrl;
1600
- if(typeof(lnurl)==="string") {
1601
- const parsedLNURL = await LNURL.getLNURL(lnurl);
1602
- if(parsedLNURL==null || parsedLNURL.tag!=="withdrawRequest")
1603
- throw new UserError("Invalid LNURL-withdraw to settle the swap");
1604
- lnurlParams = parsedLNURL;
1605
- } else {
1606
- lnurlParams = lnurl.params;
1607
- }
1608
-
1609
- if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
1610
- throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
1611
-
1612
- LNURL.useLNURLWithdraw(lnurlParams, this.pr).catch(e => this.lnurlFailSignal.abort(e));
1613
- this.lnurl = lnurlParams.url;
1614
- this.lnurlCallback = lnurlParams.callback;
1615
- this.lnurlK1 = lnurlParams.k1;
1616
- this.prPosted = true;
1617
- await this._saveAndEmit();
1618
- }
1619
-
1620
-
1621
- //////////////////////////////
1622
- //// Storage
1623
-
1624
- /**
1625
- * @inheritDoc
1626
- */
1627
- serialize(): any {
1628
- return {
1629
- ...super.serialize(),
1630
- data: this._data==null ? null : this._data.serialize(),
1631
- commitTxId: this._commitTxId,
1632
- claimTxId: this._claimTxId,
1633
- commitedAt: this._commitedAt,
1634
- btcAmountSwap: this.btcAmountSwap==null ? null : this.btcAmountSwap.toString(10),
1635
- btcAmountGas: this.btcAmountGas==null ? null : this.btcAmountGas.toString(10),
1636
- gasSwapFeeBtc: this.gasSwapFeeBtc==null ? null : this.gasSwapFeeBtc.toString(10),
1637
- gasSwapFee: this.gasSwapFee==null ? null : this.gasSwapFee.toString(10),
1638
- gasPricingInfo: serializePriceInfoType(this.gasPricingInfo),
1639
- pr: this.pr,
1640
- secret: this.secret,
1641
- lnurl: this.lnurl,
1642
- lnurlK1: this.lnurlK1,
1643
- lnurlCallback: this.lnurlCallback,
1644
- prPosted: this.prPosted,
1645
- initialSwapData: this.initialSwapData.serialize(),
1646
- usesClaimHashAsId: this.usesClaimHashAsId
1647
- };
1648
- }
1649
-
1650
-
1651
- //////////////////////////////
1652
- //// Swap ticks & sync
1653
-
1654
- /**
1655
- * Checks the swap's state on-chain and compares it to its internal state, updates/changes it according to on-chain
1656
- * data
1657
- *
1658
- * @private
1659
- */
1660
- private async syncStateFromChain(quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState): Promise<boolean> {
1661
- if(
1662
- this._state===FromBTCLNAutoSwapState.PR_PAID ||
1663
- this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED ||
1664
- this._state===FromBTCLNAutoSwapState.EXPIRED
1665
- ) {
1666
- //Check for expiry before the getCommitStatus to prevent race conditions
1667
- let quoteExpired: boolean = false;
1668
- if(this._state===FromBTCLNAutoSwapState.PR_PAID) {
1669
- quoteExpired = quoteDefinitelyExpired ?? await this._verifyQuoteDefinitelyExpired();
1670
- }
1671
-
1672
- //Check if it's already successfully paid
1673
- commitStatus ??= await this._contract.getCommitStatus(this._getInitiator(), this._data!);
1674
- if(commitStatus!=null && await this._forciblySetOnchainState(commitStatus)) return true;
1675
-
1676
- if(this._state===FromBTCLNAutoSwapState.PR_PAID) {
1677
- if(quoteExpired) {
1678
- this._state = FromBTCLNAutoSwapState.QUOTE_EXPIRED;
1679
- return true;
1680
- }
1681
- }
1682
- }
1683
-
1684
- return false;
1685
- }
1686
-
1687
- /**
1688
- * @inheritDoc
1689
- * @internal
1690
- */
1691
- _shouldFetchOnchainState(): boolean {
1692
- return this._state===FromBTCLNAutoSwapState.PR_PAID || this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED || this._state===FromBTCLNAutoSwapState.EXPIRED;
1693
- }
1694
-
1695
- /**
1696
- * @inheritDoc
1697
- * @internal
1698
- */
1699
- _shouldFetchExpiryStatus(): boolean {
1700
- return this._state===FromBTCLNAutoSwapState.PR_PAID;
1701
- }
1702
-
1703
- /**
1704
- * @inheritDoc
1705
- * @internal
1706
- */
1707
- _shouldCheckIntermediary(): boolean {
1708
- return this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED;
1709
- }
1710
-
1711
- /**
1712
- * @inheritDoc
1713
- * @internal
1714
- */
1715
- async _sync(save?: boolean, quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState, skipLpCheck?: boolean): Promise<boolean> {
1716
- let changed = false;
1717
-
1718
- if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) {
1719
- if(this._state!==FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED && this.getQuoteExpiry()<Date.now()) {
1720
- this._state = FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED;
1721
- changed ||= true;
1722
- }
1723
-
1724
- if(!skipLpCheck) try {
1725
- const result = await this._checkIntermediaryPaymentReceived(false);
1726
- if (result !== null) changed ||= true;
1727
- } catch(e) {
1728
- this.logger.error("_sync(): Failed to synchronize swap, error: ", e);
1729
- }
1730
-
1731
- if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) {
1732
- if(await this._verifyQuoteDefinitelyExpired()) {
1733
- this._state = FromBTCLNAutoSwapState.QUOTE_EXPIRED;
1734
- changed ||= true;
1735
- }
1736
- }
1737
- }
1738
-
1739
- if(await this.syncStateFromChain(quoteDefinitelyExpired, commitStatus)) changed = true;
1740
-
1741
- if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED) {
1742
- const expired = await this._contract.isExpired(this._getInitiator(), this._data!);
1743
- if(expired) {
1744
- this._state = FromBTCLNAutoSwapState.EXPIRED;
1745
- changed = true;
1746
- }
1747
- }
1748
-
1749
- if(save && changed) await this._saveAndEmit();
1750
-
1751
- if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED && this.secret!=null) await this._broadcastSecret().catch(e => {
1752
- this.logger.error("_sync(): Error when broadcasting swap secret: ", e);
1753
- });
1754
-
1755
- return changed;
1756
- }
1757
-
1758
- /**
1759
- * @inheritDoc
1760
- * @internal
1761
- */
1762
- async _forciblySetOnchainState(commitStatus: SwapCommitState): Promise<boolean> {
1763
- switch(commitStatus?.type) {
1764
- case SwapCommitStateType.PAID:
1765
- if(this._commitTxId==null && (commitStatus as any).getInitTxId!=null) this._commitTxId = await (commitStatus as any).getInitTxId();
1766
- if(this._claimTxId==null) this._claimTxId = await commitStatus.getClaimTxId();
1767
- if(this.secret==null || this.pr==null) this._setSwapSecret(await commitStatus.getClaimResult());
1768
- this._state = FromBTCLNAutoSwapState.CLAIM_CLAIMED;
1769
- return true;
1770
- case SwapCommitStateType.NOT_COMMITED:
1771
- let changed: boolean = false;
1772
- if(this._commitTxId==null && (commitStatus as any).getInitTxId!=null) {
1773
- this._commitTxId = await (commitStatus as any).getInitTxId();
1774
- changed = true;
1775
- }
1776
- if(this._refundTxId==null && commitStatus.getRefundTxId!=null) {
1777
- this._refundTxId = await commitStatus.getRefundTxId();
1778
- changed = true;
1779
- }
1780
- if(this._refundTxId!=null) {
1781
- this._state = FromBTCLNAutoSwapState.FAILED;
1782
- changed = true;
1783
- }
1784
- return changed;
1785
- case SwapCommitStateType.EXPIRED:
1786
- if(this._commitTxId==null && (commitStatus as any).getInitTxId!=null) this._commitTxId = await (commitStatus as any).getInitTxId();
1787
- if(this._refundTxId==null && commitStatus.getRefundTxId!=null) this._refundTxId = await commitStatus.getRefundTxId();
1788
- this._state = this._refundTxId==null ? FromBTCLNAutoSwapState.QUOTE_EXPIRED : FromBTCLNAutoSwapState.FAILED;
1789
- return true;
1790
- case SwapCommitStateType.COMMITED:
1791
- let save: boolean = false;
1792
- if(this._commitTxId==null && (commitStatus as any).getInitTxId!=null) {
1793
- this._commitTxId = await (commitStatus as any).getInitTxId();
1794
- save = true;
1795
- }
1796
- if(this._state!==FromBTCLNAutoSwapState.CLAIM_COMMITED && this._state!==FromBTCLNAutoSwapState.EXPIRED) {
1797
- this._commitedAt ??= Date.now();
1798
- this._state = FromBTCLNAutoSwapState.CLAIM_COMMITED;
1799
- save = true;
1800
- }
1801
- return save;
1802
- }
1803
- return false;
1804
- }
1805
-
1806
- /**
1807
- * Broadcasts the swap secret to the underlying data propagation layer (e.g. Nostr by default)
1808
- *
1809
- * @param noCheckExpiry Whether a swap expiration check should be skipped broadcasting
1810
- * @param secret An optional secret pre-image for the swap to broadcast
1811
- *
1812
- * @internal
1813
- */
1814
- async _broadcastSecret(noCheckExpiry?: boolean, secret?: string): Promise<void> {
1815
- if(this._state!==FromBTCLNAutoSwapState.CLAIM_COMMITED) throw new Error("Must be in CLAIM_COMMITED state to broadcast swap secret!");
1816
- if(this._data==null) throw new Error("Unknown data, wrong state?");
1817
-
1818
- const useSecret = secret ?? this.secret;
1819
- if(useSecret==null)
1820
- throw new Error("Swap secret pre-image not known and not provided, please provide the swap secret pre-image as an argument");
1821
- if(!this.isValidSecretPreimage(useSecret))
1822
- throw new Error("Invalid swap secret pre-image provided!");
1823
-
1824
- if(!noCheckExpiry) {
1825
- if(await this._contract.isExpired(this._getInitiator(), this._data)) throw new Error("On-chain HTLC already expired!");
1826
- }
1827
- await this.wrapper._messenger.broadcast(new SwapClaimWitnessMessage(this._data, useSecret));
1828
- }
1829
-
1830
- /**
1831
- * @inheritDoc
1832
- * @internal
1833
- */
1834
- async _tick(save?: boolean): Promise<boolean> {
1835
- switch(this._state) {
1836
- case FromBTCLNAutoSwapState.PR_CREATED:
1837
- if(this.getQuoteExpiry() < Date.now()) {
1838
- this._state = FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED;
1839
- if(save) await this._saveAndEmit();
1840
- return true;
1841
- }
1842
- break;
1843
- case FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED:
1844
- if(this.getDefinitiveExpiryTime() < Date.now()) {
1845
- this._state = FromBTCLNAutoSwapState.QUOTE_EXPIRED;
1846
- if(save) await this._saveAndEmit();
1847
- return true;
1848
- }
1849
- break;
1850
- case FromBTCLNAutoSwapState.PR_PAID:
1851
- case FromBTCLNAutoSwapState.CLAIM_COMMITED:
1852
- const expired = await this._contract.isExpired(this._getInitiator(), this._data!);
1853
- if(expired) {
1854
- this._state = FromBTCLNAutoSwapState.EXPIRED;
1855
- if(save) await this._saveAndEmit();
1856
- return true;
1857
- }
1858
- if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED) {
1859
- //Broadcast the secret over the provided messenger channel
1860
- if(this.broadcastTickCounter===0 && this.secret!=null) await this._broadcastSecret(true).catch(e => {
1861
- this.logger.warn("_tick(): Error when broadcasting swap secret: ", e);
1862
- });
1863
- this.broadcastTickCounter = (this.broadcastTickCounter + 1) % 3; //Broadcast every 3rd tick
1864
- }
1865
- break;
1866
- }
1867
-
1868
- return false;
1869
- }
1870
-
1871
- /**
1872
- * Forcibly sets the swap secret pre-image from on-chain data
1873
- *
1874
- * @internal
1875
- */
1876
- _setSwapSecret(secret: string) {
1877
- this.secret = secret;
1878
- if(this.pr==null) {
1879
- this.pr = Buffer.from(sha256(Buffer.from(secret, "hex"))).toString("hex");
1880
- }
1881
- }
1882
-
1883
- }
1
+ import {decode as bolt11Decode} from "@atomiqlabs/bolt11";
2
+ import {SwapType} from "../../../../enums/SwapType";
3
+ import {
4
+ ChainSwapType,
5
+ ChainType,
6
+ isAbstractSigner,
7
+ SwapClaimWitnessMessage,
8
+ SwapCommitState,
9
+ SwapCommitStateType,
10
+ SwapData,
11
+ } from "@atomiqlabs/base";
12
+ import {Buffer} from "buffer";
13
+ import {LNURL} from "../../../../lnurl/LNURL";
14
+ import {UserError} from "../../../../errors/UserError";
15
+ import {
16
+ IntermediaryAPI,
17
+ InvoiceStatusResponse,
18
+ InvoiceStatusResponseCodes
19
+ } from "../../../../intermediaries/apis/IntermediaryAPI";
20
+ import {IntermediaryError} from "../../../../errors/IntermediaryError";
21
+ import {extendAbortController, toBigInt} from "../../../../utils/Utils";
22
+ import {Fee} from "../../../../types/fees/Fee";
23
+ import {IAddressSwap} from "../../../IAddressSwap";
24
+ import {FromBTCLNAutoDefinition, FromBTCLNAutoWrapper} from "./FromBTCLNAutoWrapper";
25
+ import {ISwapWithGasDrop} from "../../../ISwapWithGasDrop";
26
+ import {MinimalLightningNetworkWalletInterface} from "../../../../types/wallets/MinimalLightningNetworkWalletInterface";
27
+ import {IClaimableSwap} from "../../../IClaimableSwap";
28
+ import {IEscrowSwap, IEscrowSwapInit, isIEscrowSwapInit} from "../../IEscrowSwap";
29
+ import {FeeType} from "../../../../enums/FeeType";
30
+ import {ppmToPercentage} from "../../../../types/fees/PercentagePPM";
31
+ import {TokenAmount, toTokenAmount} from "../../../../types/TokenAmount";
32
+ import {BitcoinTokens, BtcToken, SCToken} from "../../../../types/Token";
33
+ import {getLogger, LoggerType} from "../../../../utils/Logger";
34
+ import {timeoutPromise} from "../../../../utils/TimeoutUtils";
35
+ import {isLNURLWithdraw, LNURLWithdraw, LNURLWithdrawParamsWithUrl} from "../../../../types/lnurl/LNURLWithdraw";
36
+ import {
37
+ deserializePriceInfoType,
38
+ isPriceInfoType,
39
+ PriceInfoType,
40
+ serializePriceInfoType
41
+ } from "../../../../types/PriceInfoType";
42
+ import {sha256} from "@noble/hashes/sha2";
43
+ import {
44
+ SwapExecutionActionSendToAddress,
45
+ SwapExecutionActionSignSmartChainTx,
46
+ SwapExecutionActionWait
47
+ } from "../../../../types/SwapExecutionAction";
48
+ import {
49
+ SwapExecutionStepPayment,
50
+ SwapExecutionStepSettlement
51
+ } from "../../../../types/SwapExecutionStep";
52
+ import {SwapStateInfo} from "../../../../types/SwapStateInfo";
53
+
54
+ /**
55
+ * State enum for FromBTCLNAuto swaps
56
+ * @category Swaps/Lightning → Smart chain
57
+ */
58
+ export enum FromBTCLNAutoSwapState {
59
+ /**
60
+ * Swap has failed as the user didn't settle the HTLC on the destination before expiration
61
+ */
62
+ FAILED = -4,
63
+ /**
64
+ * Swap has expired for good and there is no way how it can be executed anymore
65
+ */
66
+ QUOTE_EXPIRED = -3,
67
+ /**
68
+ * A swap is almost expired, and it should be presented to the user as expired, though
69
+ * there is still a chance that it will be processed
70
+ */
71
+ QUOTE_SOFT_EXPIRED = -2,
72
+ /**
73
+ * Swap HTLC on the destination chain has expired, it is not safe anymore to settle (claim) the
74
+ * swap on the destination smart chain.
75
+ */
76
+ EXPIRED = -1,
77
+ /**
78
+ * Swap quote was created, use {@link FromBTCLNAutoSwap.getAddress} or {@link FromBTCLNAutoSwap.getHyperlink}
79
+ * to get the bolt11 lightning network invoice to pay to initiate the swap, then use the
80
+ * {@link FromBTCLNAutoSwap.waitForPayment} to wait till the lightning network payment is received
81
+ * by the intermediary (LP) and the destination HTLC escrow is created
82
+ */
83
+ PR_CREATED = 0,
84
+ /**
85
+ * Lightning network payment has been received by the intermediary (LP), but the destination chain
86
+ * HTLC escrow hasn't been created yet. Use {@link FromBTCLNAutoSwap.waitForPayment} to continue waiting
87
+ * till the destination HTLC escrow is created.
88
+ */
89
+ PR_PAID = 1,
90
+ /**
91
+ * Swap escrow HTLC has been created on the destination chain, wait for automatic settlement by the watchtowers
92
+ * using the {@link FromBTCLNAutoSwap.waitTillClaimed} function or settle manually using the
93
+ * {@link FromBTCLNAutoSwap.claim} or {@link FromBTCLNAutoSwap.txsClaim} function.
94
+ */
95
+ CLAIM_COMMITED = 2,
96
+ /**
97
+ * Swap successfully settled and funds received on the destination chain
98
+ */
99
+ CLAIM_CLAIMED = 3
100
+ }
101
+
102
+ const FromBTCLNAutoSwapStateDescription = {
103
+ [FromBTCLNAutoSwapState.FAILED]: "Swap has failed as the user didn't settle the HTLC on the destination before expiration",
104
+ [FromBTCLNAutoSwapState.QUOTE_EXPIRED]: "Swap has expired for good and there is no way how it can be executed anymore",
105
+ [FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED]: "A swap is expired, though there is still a chance that it will be processed",
106
+ [FromBTCLNAutoSwapState.EXPIRED]: "Swap HTLC on the destination chain has expired, it is not safe anymore to settle (claim) the swap on the destination smart chain.",
107
+ [FromBTCLNAutoSwapState.PR_CREATED]: "Swap quote was created, pay the bolt11 lightning network invoice to initiate the swap, then wait till the lightning network payment is received by the intermediary (LP) and the destination HTLC escrow is created",
108
+ [FromBTCLNAutoSwapState.PR_PAID]: "Lightning network payment has been received by the intermediary (LP), but the destination chain HTLC escrow hasn't been created yet. Continue waiting till the destination HTLC escrow is created.",
109
+ [FromBTCLNAutoSwapState.CLAIM_COMMITED]: "Swap escrow HTLC has been created on the destination chain, wait for automatic settlement by the watchtowers or settle manually.",
110
+ [FromBTCLNAutoSwapState.CLAIM_CLAIMED]: "Swap successfully settled and funds received on the destination chain"
111
+ };
112
+
113
+ export type FromBTCLNAutoSwapInit<T extends SwapData> = IEscrowSwapInit<T> & {
114
+ pr?: string,
115
+ secret?: string,
116
+ initialSwapData: T,
117
+
118
+ btcAmountSwap?: bigint,
119
+ btcAmountGas?: bigint,
120
+
121
+ gasSwapFeeBtc: bigint,
122
+ gasSwapFee: bigint,
123
+ gasPricingInfo?: PriceInfoType,
124
+
125
+ lnurl?: string,
126
+ lnurlK1?: string,
127
+ lnurlCallback?: string
128
+ };
129
+
130
+ export function isFromBTCLNAutoSwapInit<T extends SwapData>(obj: any): obj is FromBTCLNAutoSwapInit<T> {
131
+ return (obj.pr==null || typeof obj.pr==="string") &&
132
+ (obj.secret==null || typeof obj.secret==="string") &&
133
+ (obj.btcAmountSwap==null || typeof obj.btcAmountSwap==="bigint") &&
134
+ (obj.btcAmountGas==null || typeof obj.btcAmountGas==="bigint") &&
135
+ typeof obj.gasSwapFeeBtc==="bigint" &&
136
+ typeof obj.gasSwapFee==="bigint" &&
137
+ (obj.gasPricingInfo==null || isPriceInfoType(obj.gasPricingInfo)) &&
138
+ (obj.lnurl==null || typeof(obj.lnurl)==="string") &&
139
+ (obj.lnurlK1==null || typeof(obj.lnurlK1)==="string") &&
140
+ (obj.lnurlCallback==null || typeof(obj.lnurlCallback)==="string") &&
141
+ isIEscrowSwapInit(obj);
142
+ }
143
+
144
+ /**
145
+ * New escrow based (HTLC) swaps for Bitcoin Lightning -> Smart chain swaps not requiring manual settlement on
146
+ * the destination by the user, and instead letting the LP initiate the escrow. Permissionless watchtower network
147
+ * handles the claiming of HTLC, with the swap secret broadcasted over Nostr. Also adds a possibility for the user
148
+ * to receive a native token on the destination chain as part of the swap (a "gas drop" feature).
149
+ *
150
+ * @category Swaps/Lightning → Smart chain
151
+ */
152
+ export class FromBTCLNAutoSwap<T extends ChainType = ChainType>
153
+ extends IEscrowSwap<T, FromBTCLNAutoDefinition<T>>
154
+ implements IAddressSwap, ISwapWithGasDrop<T>, IClaimableSwap<T, FromBTCLNAutoDefinition<T>, FromBTCLNAutoSwapState> {
155
+
156
+ protected readonly TYPE: SwapType.FROM_BTCLN_AUTO = SwapType.FROM_BTCLN_AUTO;
157
+ /**
158
+ * @internal
159
+ */
160
+ protected readonly swapStateName = (state: number) => FromBTCLNAutoSwapState[state];
161
+ /**
162
+ * @internal
163
+ */
164
+ protected readonly swapStateDescription = FromBTCLNAutoSwapStateDescription;
165
+ /**
166
+ * @internal
167
+ */
168
+ protected readonly logger: LoggerType;
169
+ /**
170
+ * @internal
171
+ */
172
+ protected readonly inputToken: BtcToken<true> = BitcoinTokens.BTCLN;
173
+
174
+ /**
175
+ * Timestamp at which the HTLC was commited on the smart chain side
176
+ * @internal
177
+ */
178
+ _commitedAt?: number;
179
+
180
+ private readonly lnurlFailSignal: AbortController = new AbortController();
181
+ private readonly usesClaimHashAsId: boolean;
182
+ private readonly initialSwapData: T["Data"];
183
+
184
+ private readonly btcAmountSwap?: bigint;
185
+ private readonly btcAmountGas?: bigint;
186
+
187
+ private readonly gasSwapFeeBtc: bigint;
188
+ private readonly gasSwapFee: bigint;
189
+
190
+ private readonly gasPricingInfo?: PriceInfoType;
191
+
192
+ /**
193
+ * In case the swap is recovered from on-chain data, the pr saved here is just a payment hash,
194
+ * as it is impossible to retrieve the actual lightning network invoice paid purely from on-chain
195
+ * data
196
+ * @private
197
+ */
198
+ private pr?: string;
199
+ private secret?: string;
200
+
201
+ private lnurl?: string;
202
+ private lnurlK1?: string;
203
+ private lnurlCallback?: string;
204
+ private prPosted?: boolean = false;
205
+
206
+ private broadcastTickCounter: number = 0
207
+
208
+ /**
209
+ * Sets the LNURL data for the swap
210
+ *
211
+ * @internal
212
+ */
213
+ _setLNURLData(lnurl: string, lnurlK1: string, lnurlCallback: string) {
214
+ this.lnurl = lnurl;
215
+ this.lnurlK1 = lnurlK1;
216
+ this.lnurlCallback = lnurlCallback;
217
+ }
218
+
219
+ constructor(wrapper: FromBTCLNAutoWrapper<T>, init: FromBTCLNAutoSwapInit<T["Data"]>);
220
+ constructor(wrapper: FromBTCLNAutoWrapper<T>, obj: any);
221
+ constructor(
222
+ wrapper: FromBTCLNAutoWrapper<T>,
223
+ initOrObject: FromBTCLNAutoSwapInit<T["Data"]> | any
224
+ ) {
225
+ if(isFromBTCLNAutoSwapInit(initOrObject) && initOrObject.url!=null) initOrObject.url += "/frombtcln_auto";
226
+ super(wrapper, initOrObject);
227
+ if(isFromBTCLNAutoSwapInit(initOrObject)) {
228
+ this._state = FromBTCLNAutoSwapState.PR_CREATED;
229
+ this.pr = initOrObject.pr;
230
+ this.secret = initOrObject.secret;
231
+ this.initialSwapData = initOrObject.initialSwapData;
232
+ this.btcAmountSwap = initOrObject.btcAmountSwap;
233
+ this.btcAmountGas = initOrObject.btcAmountGas;
234
+ this.gasSwapFeeBtc = initOrObject.gasSwapFeeBtc;
235
+ this.gasSwapFee = initOrObject.gasSwapFee;
236
+ this.gasPricingInfo = initOrObject.gasPricingInfo;
237
+ this.lnurl = initOrObject.lnurl;
238
+ this.lnurlK1 = initOrObject.lnurlK1;
239
+ this.lnurlCallback = initOrObject.lnurlCallback;
240
+ this.usesClaimHashAsId = true;
241
+ } else {
242
+ this.pr = initOrObject.pr;
243
+ this.secret = initOrObject.secret;
244
+
245
+ if(initOrObject.initialSwapData==null) {
246
+ this.initialSwapData = this._data!;
247
+ } else {
248
+ this.initialSwapData = SwapData.deserialize<T["Data"]>(initOrObject.initialSwapData);
249
+ }
250
+
251
+ this.btcAmountSwap = toBigInt(initOrObject.btcAmountSwap);
252
+ this.btcAmountGas = toBigInt(initOrObject.btcAmountGas);
253
+ this.gasSwapFeeBtc = toBigInt(initOrObject.gasSwapFeeBtc);
254
+ this.gasSwapFee = toBigInt(initOrObject.gasSwapFee);
255
+ this.gasPricingInfo = deserializePriceInfoType(initOrObject.gasPricingInfo);
256
+
257
+ this._commitTxId = initOrObject.commitTxId;
258
+ this._claimTxId = initOrObject.claimTxId;
259
+ this._commitedAt = initOrObject.commitedAt;
260
+
261
+ this.lnurl = initOrObject.lnurl;
262
+ this.lnurlK1 = initOrObject.lnurlK1;
263
+ this.lnurlCallback = initOrObject.lnurlCallback;
264
+ this.prPosted = initOrObject.prPosted;
265
+ this.usesClaimHashAsId = initOrObject.usesClaimHashAsId ?? false;
266
+ }
267
+ this.tryRecomputeSwapPrice();
268
+ this.logger = getLogger("FromBTCLNAuto("+this.getIdentifierHashString()+"): ");
269
+ }
270
+
271
+ /**
272
+ * @inheritDoc
273
+ * @internal
274
+ */
275
+ protected getSwapData(): T["Data"] {
276
+ return this._data ?? this.initialSwapData;
277
+ }
278
+
279
+ /**
280
+ * @inheritDoc
281
+ * @internal
282
+ */
283
+ protected upgradeVersion() { /*NOOP*/ }
284
+
285
+ /**
286
+ * @inheritDoc
287
+ * @internal
288
+ */
289
+ protected tryRecomputeSwapPrice() {
290
+ if(this.pricingInfo==null || this.btcAmountSwap==null) return;
291
+ if(this.pricingInfo.swapPriceUSatPerToken==null) {
292
+ const priceUsdPerBtc = this.pricingInfo.realPriceUsdPerBitcoin;
293
+ this.pricingInfo = this.wrapper._prices.recomputePriceInfoReceive(
294
+ this.chainIdentifier,
295
+ this.btcAmountSwap,
296
+ this.pricingInfo.satsBaseFee,
297
+ this.pricingInfo.feePPM,
298
+ this.getOutputAmountWithoutFee(),
299
+ this.getSwapData().getToken()
300
+ );
301
+ this.pricingInfo.realPriceUsdPerBitcoin = priceUsdPerBtc;
302
+ }
303
+ }
304
+
305
+
306
+ //////////////////////////////
307
+ //// Pricing
308
+
309
+ /**
310
+ * @inheritDoc
311
+ */
312
+ async refreshPriceData(): Promise<void> {
313
+ if(this.pricingInfo==null || this.btcAmountSwap==null) return;
314
+ const usdPricePerBtc = this.pricingInfo.realPriceUsdPerBitcoin;
315
+ this.pricingInfo = await this.wrapper._prices.isValidAmountReceive(
316
+ this.chainIdentifier,
317
+ this.btcAmountSwap,
318
+ this.pricingInfo.satsBaseFee,
319
+ this.pricingInfo.feePPM,
320
+ this.getOutputAmountWithoutFee(),
321
+ this.getSwapData().getToken(),
322
+ undefined,
323
+ undefined,
324
+ this.swapFeeBtc
325
+ );
326
+ this.pricingInfo.realPriceUsdPerBitcoin = usdPricePerBtc;
327
+ }
328
+
329
+
330
+ //////////////////////////////
331
+ //// Getters & utils
332
+
333
+ /**
334
+ * @inheritDoc
335
+ * @internal
336
+ */
337
+ _getEscrowHash(): string | null {
338
+ //Use claim hash in case the data is not yet known
339
+ return this._data == null ? this.initialSwapData?.getClaimHash() : this._data?.getEscrowHash();
340
+ }
341
+
342
+ /**
343
+ * @inheritDoc
344
+ * @internal
345
+ */
346
+ _getInitiator(): string {
347
+ return this.getSwapData().getClaimer();
348
+ }
349
+
350
+ /**
351
+ * @inheritDoc
352
+ */
353
+ getId(): string {
354
+ return this.getIdentifierHashString();
355
+ }
356
+
357
+ /**
358
+ * @inheritDoc
359
+ */
360
+ getOutputAddress(): string | null {
361
+ return this._getInitiator();
362
+ }
363
+
364
+ /**
365
+ * @inheritDoc
366
+ */
367
+ getOutputTxId(): string | null {
368
+ return this._claimTxId ?? null;
369
+ }
370
+
371
+ /**
372
+ * @inheritDoc
373
+ */
374
+ requiresAction(): boolean {
375
+ return this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED;
376
+ }
377
+
378
+ /**
379
+ * @inheritDoc
380
+ * @internal
381
+ */
382
+ protected getIdentifierHashString(): string {
383
+ const id: string = this.usesClaimHashAsId
384
+ ? this.getClaimHash()
385
+ : this.getPaymentHash()!.toString("hex");
386
+ if(this._randomNonce==null) return id;
387
+ return id + this._randomNonce;
388
+ }
389
+
390
+ /**
391
+ * Returns the payment hash of the swap and lightning network invoice, or `null` if not known (i.e. if
392
+ * the swap was recovered from on-chain data, the payment hash might not be known)
393
+ *
394
+ * @internal
395
+ */
396
+ protected getPaymentHash(): Buffer | null {
397
+ if(this.pr==null) return null;
398
+ if(this.pr.toLowerCase().startsWith("ln")) {
399
+ const parsed = bolt11Decode(this.pr);
400
+ if(parsed.tagsObject.payment_hash==null) throw new Error("Swap invoice has no payment hash field!");
401
+ return Buffer.from(parsed.tagsObject.payment_hash, "hex");
402
+ }
403
+ return Buffer.from(this.pr, "hex");
404
+ }
405
+
406
+ /**
407
+ * @inheritDoc
408
+ */
409
+ getInputAddress(): string | null {
410
+ return this.lnurl ?? this.pr ?? null;
411
+ }
412
+
413
+ /**
414
+ * @inheritDoc
415
+ */
416
+ getInputTxId(): string | null {
417
+ const paymentHash = this.getPaymentHash();
418
+ if(paymentHash==null) return null;
419
+ return paymentHash.toString("hex");
420
+ }
421
+
422
+ /**
423
+ * Returns the lightning network BOLT11 invoice that needs to be paid as an input to the swap
424
+ */
425
+ getAddress(): string {
426
+ return this.pr ?? "";
427
+ }
428
+
429
+ /**
430
+ * @inheritDoc
431
+ */
432
+ getHyperlink(): string {
433
+ return this.pr==null ? "" : "lightning:"+this.pr.toUpperCase();
434
+ }
435
+
436
+ /**
437
+ * Returns the timeout time (in UNIX milliseconds) when the swap will definitelly be considered as expired
438
+ * if the LP doesn't make it expired sooner
439
+ */
440
+ getDefinitiveExpiryTime(): number {
441
+ if(this.pr==null || !this.pr.toLowerCase().startsWith("ln")) return 0;
442
+ const decoded = bolt11Decode(this.pr);
443
+ if(decoded.tagsObject.min_final_cltv_expiry==null) throw new Error("Swap invoice doesn't contain final ctlv delta field!");
444
+ if(decoded.timeExpireDate==null) throw new Error("Swap invoice doesn't contain expiry date field!");
445
+ const finalCltvExpiryDelta = decoded.tagsObject.min_final_cltv_expiry ?? 144;
446
+ const finalCltvExpiryDelay = finalCltvExpiryDelta * this.wrapper._options.bitcoinBlocktime * this.wrapper._options.safetyFactor;
447
+ return (decoded.timeExpireDate + finalCltvExpiryDelay)*1000;
448
+ }
449
+
450
+ /**
451
+ * Returns timeout time (in UNIX milliseconds) when the swap htlc will expire
452
+ */
453
+ getHtlcTimeoutTime(): number | null {
454
+ return this._data==null ? null : Number(this.wrapper._getHtlcTimeout(this._data))*1000;
455
+ }
456
+
457
+ /**
458
+ * @inheritDoc
459
+ */
460
+ isFinished(): boolean {
461
+ return this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED || this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED || this._state===FromBTCLNAutoSwapState.FAILED;
462
+ }
463
+
464
+ /**
465
+ * @inheritDoc
466
+ */
467
+ isClaimable(): boolean {
468
+ return this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED;
469
+ }
470
+
471
+ /**
472
+ * @inheritDoc
473
+ */
474
+ isSuccessful(): boolean {
475
+ return this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED;
476
+ }
477
+
478
+ /**
479
+ * @inheritDoc
480
+ */
481
+ isFailed(): boolean {
482
+ return this._state===FromBTCLNAutoSwapState.FAILED || this._state===FromBTCLNAutoSwapState.EXPIRED;
483
+ }
484
+
485
+ /**
486
+ * @inheritDoc
487
+ */
488
+ isInProgress(): boolean {
489
+ return (this._state===FromBTCLNAutoSwapState.PR_CREATED && this.initiated) ||
490
+ (this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED && this.initiated) ||
491
+ this._state===FromBTCLNAutoSwapState.PR_PAID ||
492
+ this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED;
493
+ }
494
+
495
+ /**
496
+ * @inheritDoc
497
+ */
498
+ isQuoteExpired(): boolean {
499
+ return this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED;
500
+ }
501
+
502
+ /**
503
+ * @inheritDoc
504
+ */
505
+ isQuoteSoftExpired(): boolean {
506
+ return this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED;
507
+ }
508
+
509
+ /**
510
+ * @inheritDoc
511
+ */
512
+ _verifyQuoteDefinitelyExpired(): Promise<boolean> {
513
+ return Promise.resolve(this.getDefinitiveExpiryTime()<Date.now());
514
+ }
515
+
516
+ /**
517
+ * @inheritDoc
518
+ */
519
+ _verifyQuoteValid(): Promise<boolean> {
520
+ return Promise.resolve(this.getQuoteExpiry()>Date.now());
521
+ }
522
+
523
+
524
+ //////////////////////////////
525
+ //// Amounts & fees
526
+
527
+ /**
528
+ * Returns the satoshi amount of the lightning network invoice, or `null` if the lightning network
529
+ * invoice is not known (i.e. when the swap was recovered from on-chain data, the paid invoice
530
+ * cannot be recovered because it is purely off-chain)
531
+ *
532
+ * @internal
533
+ */
534
+ protected getLightningInvoiceSats(): bigint | null {
535
+ if(this.pr==null || !this.pr.toLowerCase().startsWith("ln")) return null;
536
+
537
+ const parsed = bolt11Decode(this.pr);
538
+ if(parsed.millisatoshis==null) throw new Error("Swap invoice doesn't contain msat amount field!");
539
+ return (BigInt(parsed.millisatoshis) + 999n) / 1000n;
540
+ }
541
+
542
+ /**
543
+ * Returns the watchtower fee paid in BTC satoshis, or null if known (i.e. if the swap was recovered from
544
+ * on-chain data)
545
+ *
546
+ * @protected
547
+ */
548
+ protected getWatchtowerFeeAmountBtc(): bigint | null {
549
+ if(this.btcAmountGas==null) return null;
550
+ return (this.btcAmountGas - this.gasSwapFeeBtc) * this.getSwapData().getClaimerBounty() / this.getSwapData().getTotalDeposit();
551
+ }
552
+
553
+ /**
554
+ * Returns the input amount for the actual swap (excluding the input amount used to cover the "gas drop"
555
+ * part of the swap), excluding fees
556
+ *
557
+ * @internal
558
+ */
559
+ protected getInputSwapAmountWithoutFee(): bigint | null {
560
+ if(this.btcAmountSwap==null) return null;
561
+ return this.btcAmountSwap - this.swapFeeBtc;
562
+ }
563
+
564
+ /**
565
+ * Returns the input amount purely for the "gas drop" part of the swap (this much BTC in sats will be
566
+ * swapped into the native gas token on the destination chain), excluding fees
567
+ *
568
+ * @internal
569
+ */
570
+ protected getInputGasAmountWithoutFee(): bigint | null {
571
+ if(this.btcAmountGas==null) return null;
572
+ return this.btcAmountGas - this.gasSwapFeeBtc;
573
+ }
574
+
575
+ /**
576
+ * Get total btc amount in sats on the input, excluding the swap fee and watchtower fee
577
+ *
578
+ * @internal
579
+ */
580
+ protected getInputAmountWithoutFee(): bigint | null {
581
+ if(this.btcAmountGas==null || this.btcAmountSwap==null) return null;
582
+ return this.getInputSwapAmountWithoutFee()! + this.getInputGasAmountWithoutFee()! - this.getWatchtowerFeeAmountBtc()!;
583
+ }
584
+
585
+ /**
586
+ * Returns the "would be" output amount if the swap charged no swap fee
587
+ *
588
+ * @internal
589
+ */
590
+ protected getOutputAmountWithoutFee(): bigint {
591
+ return this.getSwapData().getAmount() + this.swapFee;
592
+ }
593
+
594
+ /**
595
+ * @inheritDoc
596
+ */
597
+ getInputToken(): BtcToken<true> {
598
+ return BitcoinTokens.BTCLN;
599
+ }
600
+
601
+ /**
602
+ * @inheritDoc
603
+ */
604
+ getInput(): TokenAmount<BtcToken<true>> {
605
+ return toTokenAmount(this.getLightningInvoiceSats(), this.inputToken, this.wrapper._prices, this.pricingInfo);
606
+ }
607
+
608
+ /**
609
+ * @inheritDoc
610
+ */
611
+ getInputWithoutFee(): TokenAmount<BtcToken<true>> {
612
+ return toTokenAmount(this.getInputAmountWithoutFee(), this.inputToken, this.wrapper._prices, this.pricingInfo);
613
+ }
614
+
615
+ /**
616
+ * @inheritDoc
617
+ */
618
+ getOutputToken(): SCToken<T["ChainId"]> {
619
+ return this.wrapper._tokens[this.getSwapData().getToken()];
620
+ }
621
+
622
+ /**
623
+ * @inheritDoc
624
+ */
625
+ getOutput(): TokenAmount<SCToken<T["ChainId"]>, true> {
626
+ return toTokenAmount(this.getSwapData().getAmount(), this.wrapper._tokens[this.getSwapData().getToken()], this.wrapper._prices, this.pricingInfo);
627
+ }
628
+
629
+ /**
630
+ * @inheritDoc
631
+ */
632
+ getGasDropOutput(): TokenAmount<SCToken<T["ChainId"]>, true> {
633
+ return toTokenAmount(
634
+ this.getSwapData().getSecurityDeposit() - this.getSwapData().getClaimerBounty(),
635
+ this.wrapper._tokens[this.getSwapData().getDepositToken()], this.wrapper._prices, this.gasPricingInfo
636
+ );
637
+ }
638
+
639
+ /**
640
+ * Returns the swap fee charged by the intermediary (LP) on this swap
641
+ *
642
+ * @internal
643
+ */
644
+ protected getSwapFee(): Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>> {
645
+ if(this.pricingInfo==null) throw new Error("No pricing info known, cannot estimate fee!");
646
+
647
+ const outputToken = this.wrapper._tokens[this.getSwapData().getToken()];
648
+ const gasSwapFeeInOutputToken = this.gasSwapFeeBtc
649
+ * (10n ** BigInt(outputToken.decimals))
650
+ * 1_000_000n
651
+ / this.pricingInfo.swapPriceUSatPerToken;
652
+
653
+ const feeWithoutBaseFee = this.gasSwapFeeBtc + this.swapFeeBtc - this.pricingInfo.satsBaseFee;
654
+ const inputSats = this.getLightningInvoiceSats();
655
+ const swapFeePPM = inputSats!=null
656
+ ? feeWithoutBaseFee * 1000000n / (inputSats - this.swapFeeBtc - this.gasSwapFeeBtc)
657
+ : 0n;
658
+
659
+ const amountInSrcToken = toTokenAmount(this.swapFeeBtc + this.gasSwapFeeBtc, BitcoinTokens.BTCLN, this.wrapper._prices, this.pricingInfo);
660
+ return {
661
+ amountInSrcToken,
662
+ amountInDstToken: toTokenAmount(this.swapFee + gasSwapFeeInOutputToken, outputToken, this.wrapper._prices, this.pricingInfo),
663
+ currentUsdValue: amountInSrcToken.currentUsdValue,
664
+ pastUsdValue: amountInSrcToken.pastUsdValue,
665
+ usdValue: amountInSrcToken.usdValue,
666
+ composition: {
667
+ base: toTokenAmount(this.pricingInfo.satsBaseFee, BitcoinTokens.BTCLN, this.wrapper._prices, this.pricingInfo),
668
+ percentage: ppmToPercentage(swapFeePPM)
669
+ }
670
+ };
671
+ }
672
+
673
+ /**
674
+ * Returns the fee to be paid to watchtowers on the destination chain to automatically
675
+ * process and settle this swap without requiring any user interaction
676
+ *
677
+ * @internal
678
+ */
679
+ protected getWatchtowerFee(): Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>> {
680
+ if(this.pricingInfo==null) throw new Error("No pricing info known, cannot estimate fee!");
681
+
682
+ const btcWatchtowerFee = this.getWatchtowerFeeAmountBtc();
683
+ const outputToken = this.wrapper._tokens[this.getSwapData().getToken()];
684
+ const watchtowerFeeInOutputToken = btcWatchtowerFee==null ? 0n : btcWatchtowerFee
685
+ * (10n ** BigInt(outputToken.decimals))
686
+ * 1_000_000n
687
+ / this.pricingInfo.swapPriceUSatPerToken;
688
+
689
+ const amountInSrcToken = toTokenAmount(btcWatchtowerFee, BitcoinTokens.BTCLN, this.wrapper._prices, this.pricingInfo);
690
+ return {
691
+ amountInSrcToken,
692
+ amountInDstToken: toTokenAmount(watchtowerFeeInOutputToken, outputToken, this.wrapper._prices, this.pricingInfo),
693
+ currentUsdValue: amountInSrcToken.currentUsdValue,
694
+ usdValue: amountInSrcToken.usdValue,
695
+ pastUsdValue: amountInSrcToken.pastUsdValue
696
+ };
697
+ }
698
+
699
+ /**
700
+ * @inheritDoc
701
+ */
702
+ getFee(): Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>> {
703
+ const swapFee = this.getSwapFee();
704
+ const watchtowerFee = this.getWatchtowerFee();
705
+
706
+ const amountInSrcToken = toTokenAmount(
707
+ swapFee.amountInSrcToken.rawAmount + watchtowerFee.amountInSrcToken.rawAmount,
708
+ BitcoinTokens.BTCLN, this.wrapper._prices, this.pricingInfo
709
+ );
710
+ return {
711
+ amountInSrcToken,
712
+ amountInDstToken: toTokenAmount(
713
+ swapFee.amountInDstToken.rawAmount + watchtowerFee.amountInDstToken.rawAmount,
714
+ this.wrapper._tokens[this.getSwapData().getToken()], this.wrapper._prices, this.pricingInfo
715
+ ),
716
+ currentUsdValue: amountInSrcToken.currentUsdValue,
717
+ usdValue: amountInSrcToken.usdValue,
718
+ pastUsdValue: amountInSrcToken.pastUsdValue
719
+ };
720
+ }
721
+
722
+ /**
723
+ * @inheritDoc
724
+ */
725
+ getFeeBreakdown(): [
726
+ {type: FeeType.SWAP, fee: Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>>},
727
+ {type: FeeType.NETWORK_OUTPUT, fee: Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>>}
728
+ ] {
729
+ return [
730
+ {
731
+ type: FeeType.SWAP,
732
+ fee: this.getSwapFee()
733
+ },
734
+ {
735
+ type: FeeType.NETWORK_OUTPUT,
736
+ fee: this.getWatchtowerFee()
737
+ }
738
+ ];
739
+ }
740
+
741
+ private isValidSecretPreimage(secret: string) {
742
+ const paymentHash = Buffer.from(sha256(Buffer.from(secret, "hex")));
743
+ const claimHash = this._contract.getHashForHtlc(paymentHash).toString("hex");
744
+ return this.getSwapData().getClaimHash()===claimHash;
745
+ }
746
+
747
+ /**
748
+ * Sets the secret preimage for the swap, in case it is not known already
749
+ *
750
+ * @param secret Secret preimage that matches the expected payment hash
751
+ *
752
+ * @throws {Error} If an invalid secret preimage is provided
753
+ */
754
+ async setSecretPreimage(secret: string) {
755
+ if(!this.isValidSecretPreimage(secret)) throw new Error("Invalid secret preimage provided, hash doesn't match!");
756
+ this.secret = secret;
757
+ await this._broadcastSecret().catch(e => this.logger.error("setSecretPreimage(): Failed to broadcast swap secret: ", e));
758
+ }
759
+
760
+ /**
761
+ * Returns whether the secret preimage for this swap is known
762
+ */
763
+ hasSecretPreimage(): boolean {
764
+ return this.secret != null;
765
+ }
766
+
767
+
768
+ //////////////////////////////
769
+ //// Execution
770
+
771
+ /**
772
+ * Executes the swap with the provided bitcoin lightning network wallet or LNURL
773
+ *
774
+ * @param walletOrLnurlWithdraw Bitcoin lightning wallet to use to pay the lightning network invoice, or an LNURL-withdraw
775
+ * link, wallet is not required and the LN invoice can be paid externally as well (just pass null or undefined here)
776
+ * @param callbacks Callbacks to track the progress of the swap
777
+ * @param options Optional options for the swap like AbortSignal, and timeouts/intervals
778
+ * @param options.secret A swap secret to broadcast to watchtowers, generally only needed if the swap
779
+ * was recovered from on-chain data, or the pre-image was generated outside the SDK
780
+ *
781
+ * @returns {boolean} Whether a swap was settled automatically by swap watchtowers or requires manual claim by the
782
+ * user, in case `false` is returned the user should call `swap.claim()` to settle the swap on the destination manually
783
+ */
784
+ async execute(
785
+ walletOrLnurlWithdraw?: MinimalLightningNetworkWalletInterface | LNURLWithdraw | string | null | undefined,
786
+ callbacks?: {
787
+ onSourceTransactionReceived?: (sourceTxId: string) => void,
788
+ onSwapSettled?: (destinationTxId: string) => void
789
+ },
790
+ options?: {
791
+ abortSignal?: AbortSignal,
792
+ lightningTxCheckIntervalSeconds?: number,
793
+ maxWaitTillAutomaticSettlementSeconds?: number,
794
+ secret?: string
795
+ }
796
+ ): Promise<boolean> {
797
+ if(this._state===FromBTCLNAutoSwapState.FAILED) throw new Error("Swap failed!");
798
+ if(this._state===FromBTCLNAutoSwapState.EXPIRED) throw new Error("Swap HTLC expired!");
799
+ if(this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) throw new Error("Swap quote expired!");
800
+ if(this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED) throw new Error("Swap already settled!");
801
+
802
+ let abortSignal = options?.abortSignal;
803
+
804
+ if(this._state===FromBTCLNAutoSwapState.PR_CREATED) {
805
+ if(walletOrLnurlWithdraw!=null && this.lnurl==null) {
806
+ if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
807
+ throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
808
+
809
+ if(typeof(walletOrLnurlWithdraw)==="string" || isLNURLWithdraw(walletOrLnurlWithdraw)) {
810
+ await this.settleWithLNURLWithdraw(walletOrLnurlWithdraw);
811
+ } else {
812
+ const paymentPromise = walletOrLnurlWithdraw.payInvoice(this.pr);
813
+
814
+ const abortController = new AbortController();
815
+ paymentPromise.catch(e => abortController.abort(e));
816
+ if(options?.abortSignal!=null) options.abortSignal.addEventListener("abort", () => abortController.abort(options?.abortSignal?.reason));
817
+ abortSignal = abortController.signal;
818
+ }
819
+ }
820
+ }
821
+
822
+ if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.PR_PAID) {
823
+ const paymentSuccess = await this.waitForPayment(callbacks?.onSourceTransactionReceived, options?.lightningTxCheckIntervalSeconds, abortSignal);
824
+ if (!paymentSuccess) throw new Error("Failed to receive lightning network payment");
825
+ }
826
+
827
+ if((this._state as FromBTCLNAutoSwapState)===FromBTCLNAutoSwapState.CLAIM_CLAIMED) return true;
828
+
829
+ if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED) {
830
+ if(this.secret==null && options?.secret==null)
831
+ throw new Error("Tried to wait till settlement, but no secret pre-image is known, please pass the secret pre-image as an argument!");
832
+ const success = await this.waitTillClaimed(options?.maxWaitTillAutomaticSettlementSeconds ?? 60, options?.abortSignal, options?.secret);
833
+ if (success && callbacks?.onSwapSettled != null) callbacks.onSwapSettled(this.getOutputTxId()!);
834
+ return success;
835
+ }
836
+
837
+ throw new Error("Invalid state reached!");
838
+ }
839
+
840
+ /**
841
+ * @internal
842
+ */
843
+ protected async _getExecutionStatus(options?: {
844
+ maxWaitTillAutomaticSettlementSeconds?: number,
845
+ secret?: string
846
+ }) {
847
+ if(options?.secret!=null) await this.setSecretPreimage(options.secret);
848
+
849
+ const state = this._state;
850
+ const now = Date.now();
851
+
852
+ let lightningPaymentStatus: SwapExecutionStepPayment<"LIGHTNING">["status"] = "inactive";
853
+ let destinationSettlementStatus: SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">["status"] = "inactive";
854
+ let buildCurrentAction: (actionOptions?: {
855
+ manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"]
856
+ }) => Promise<
857
+ SwapExecutionActionSendToAddress<true> |
858
+ SwapExecutionActionWait<"LP" | "SETTLEMENT"> |
859
+ SwapExecutionActionSignSmartChainTx<T> |
860
+ undefined
861
+ > = async () => undefined;
862
+
863
+ switch(state) {
864
+ case FromBTCLNAutoSwapState.PR_CREATED: {
865
+ const quoteValid = await this._verifyQuoteValid();
866
+ lightningPaymentStatus = quoteValid ? "awaiting" : "soft_expired";
867
+ if(quoteValid && this.pr!=null && this.pr.toLowerCase().startsWith("ln")) {
868
+ buildCurrentAction = this._buildLightningPaymentAction.bind(this);
869
+ }
870
+ break;
871
+ }
872
+ case FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED:
873
+ lightningPaymentStatus = "soft_expired";
874
+ break;
875
+ case FromBTCLNAutoSwapState.QUOTE_EXPIRED:
876
+ lightningPaymentStatus = "expired";
877
+ break;
878
+ case FromBTCLNAutoSwapState.PR_PAID:
879
+ lightningPaymentStatus = "received";
880
+ destinationSettlementStatus = "waiting_lp";
881
+ buildCurrentAction = this._buildWaitLpAction.bind(this);
882
+ break;
883
+ case FromBTCLNAutoSwapState.CLAIM_COMMITED:
884
+ lightningPaymentStatus = "received";
885
+ if(
886
+ this._commitedAt==null ||
887
+ options?.maxWaitTillAutomaticSettlementSeconds===0 ||
888
+ (now - this._commitedAt) > (options?.maxWaitTillAutomaticSettlementSeconds ?? 60)*1000
889
+ ) {
890
+ destinationSettlementStatus = "awaiting_manual";
891
+ if(this.hasSecretPreimage()) {
892
+ //TODO: Maybe add an action that would prompt the user to reveal the pre-image
893
+ buildCurrentAction = this._buildClaimSmartChainTxAction.bind(this);
894
+ }
895
+ } else {
896
+ destinationSettlementStatus = "awaiting_automatic";
897
+ //TODO: Maybe add an action that would prompt the user to reveal the pre-image
898
+ buildCurrentAction = this._buildWaitSettlementAction.bind(this, options?.maxWaitTillAutomaticSettlementSeconds);
899
+ }
900
+ break;
901
+ case FromBTCLNAutoSwapState.CLAIM_CLAIMED:
902
+ lightningPaymentStatus = "confirmed";
903
+ destinationSettlementStatus = "settled";
904
+ break;
905
+ case FromBTCLNAutoSwapState.EXPIRED:
906
+ case FromBTCLNAutoSwapState.FAILED:
907
+ lightningPaymentStatus = "expired";
908
+ destinationSettlementStatus = "expired";
909
+ break;
910
+ }
911
+
912
+ return {
913
+ steps: [
914
+ {
915
+ type: "Payment",
916
+ side: "source",
917
+ chain: "LIGHTNING",
918
+ title: "Lightning payment",
919
+ description: "Pay the Lightning network invoice to initiate the swap",
920
+ status: lightningPaymentStatus,
921
+ initTxId: this.getInputTxId(),
922
+ settleTxId: lightningPaymentStatus==="confirmed" ? this.getInputTxId() : undefined
923
+ },
924
+ {
925
+ type: "Settlement",
926
+ side: "destination",
927
+ chain: this.chainIdentifier,
928
+ title: "Destination settlement",
929
+ description: `Wait for the LP to initiate on the ${this.chainIdentifier} side, then wait for automatic settlement, or settle manually if it takes too long`,
930
+ status: destinationSettlementStatus,
931
+ initTxId: this._commitTxId,
932
+ settleTxId: this._claimTxId
933
+ }
934
+ ] as [
935
+ SwapExecutionStepPayment<"LIGHTNING">,
936
+ SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
937
+ ],
938
+ buildCurrentAction,
939
+ state
940
+ };
941
+ }
942
+
943
+ /**
944
+ * @internal
945
+ */
946
+ private async _buildLightningPaymentAction(): Promise<SwapExecutionActionSendToAddress<true>> {
947
+ return {
948
+ type: "SendToAddress",
949
+ name: "Deposit on Lightning",
950
+ description: "Pay the lightning network invoice to initiate the swap",
951
+ chain: "LIGHTNING",
952
+ txs: [{
953
+ type: "BOLT11_PAYMENT_REQUEST",
954
+ address: this.getAddress(),
955
+ hyperlink: this.getHyperlink(),
956
+ amount: this.getInput()
957
+ }],
958
+ waitForTransactions: async (
959
+ maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal
960
+ ) => {
961
+ const abortController = extendAbortController(
962
+ abortSignal, maxWaitTimeSeconds, "Timed out waiting for lightning payment"
963
+ );
964
+ const success = await this._waitForLpPaymentReceived(pollIntervalSeconds, abortController.signal);
965
+ if(!success) throw new Error("Quote expired while waiting for Lightning payment");
966
+
967
+ return this.getInputTxId()!;
968
+ }
969
+ } as SwapExecutionActionSendToAddress<true>;
970
+ }
971
+
972
+ /**
973
+ * @internal
974
+ */
975
+ private async _buildWaitLpAction(): Promise<SwapExecutionActionWait<"LP">> {
976
+ return {
977
+ type: "Wait",
978
+ name: "Awaiting LP payout",
979
+ description: "Wait for the LP to create the swap HTLC on the destination smart chain",
980
+ pollTimeSeconds: 5,
981
+ expectedTimeSeconds: 10,
982
+ wait: async (
983
+ maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal
984
+ ) => {
985
+ const abortController = extendAbortController(
986
+ abortSignal, maxWaitTimeSeconds, "Timed out waiting for LP payout"
987
+ );
988
+ await this.waitTillCommited(pollIntervalSeconds, abortController.signal);
989
+ }
990
+ } as SwapExecutionActionWait<"LP">;
991
+ }
992
+
993
+ /**
994
+ * @internal
995
+ */
996
+ private async _buildWaitSettlementAction(
997
+ maxWaitTillAutomaticSettlementSeconds?: number
998
+ ): Promise<SwapExecutionActionWait<"SETTLEMENT">> {
999
+ return {
1000
+ type: "Wait",
1001
+ name: "Automatic settlement",
1002
+ description: "Wait for automatic settlement by the watchtower",
1003
+ pollTimeSeconds: 5,
1004
+ expectedTimeSeconds: 10,
1005
+ wait: async (
1006
+ maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal
1007
+ ) => {
1008
+ await this.waitTillClaimed(
1009
+ maxWaitTimeSeconds ?? maxWaitTillAutomaticSettlementSeconds ?? 60,
1010
+ abortSignal,
1011
+ undefined,
1012
+ pollIntervalSeconds
1013
+ );
1014
+ }
1015
+ } as SwapExecutionActionWait<"SETTLEMENT">;
1016
+ }
1017
+
1018
+ /**
1019
+ * @inheritDoc
1020
+ * @internal
1021
+ */
1022
+ async _submitExecutionTransactions(
1023
+ txs: (T["SignedTXType"] | string)[],
1024
+ abortSignal?: AbortSignal,
1025
+ requiredStates?: FromBTCLNAutoSwapState[],
1026
+ idempotent?: boolean
1027
+ ): Promise<string[]> {
1028
+ const parsedTxs: T["SignedTXType"][] = [];
1029
+ for(let tx of txs) {
1030
+ parsedTxs.push(typeof(tx)==="string" ? await this.wrapper._chain.deserializeSignedTx(tx) : tx);
1031
+ }
1032
+
1033
+ if(idempotent) {
1034
+ // Handle idempotent calls
1035
+ if(this.wrapper._chain.getTxId!=null) {
1036
+ const txIds = await Promise.all(parsedTxs.map(tx => this.wrapper._chain.getTxId!(tx)));
1037
+ const foundTxId = txIds.find(txId => this._claimTxId===txId);
1038
+ if(foundTxId!=null) return txIds;
1039
+ }
1040
+ }
1041
+
1042
+ if(requiredStates!=null && !requiredStates.includes(this._state)) throw new Error("Swap state has changed before transactions were submitted!");
1043
+
1044
+ if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED) {
1045
+ const txIds = await this.wrapper._chain.sendSignedAndConfirm(parsedTxs, true, abortSignal, false);
1046
+ await this.waitTillClaimed(undefined, abortSignal);
1047
+ return txIds;
1048
+ }
1049
+
1050
+ throw new Error("Invalid swap state for transaction submission!");
1051
+ }
1052
+
1053
+ /**
1054
+ * @internal
1055
+ */
1056
+ private async _buildClaimSmartChainTxAction(actionOptions?: {
1057
+ manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
1058
+ secret?: string
1059
+ }): Promise<SwapExecutionActionSignSmartChainTx<T>> {
1060
+ const signerAddress =
1061
+ await this.wrapper._getSignerAddress(actionOptions?.manualSettlementSmartChainSigner);
1062
+
1063
+ return {
1064
+ type: "SignSmartChainTransaction",
1065
+ name: "Settle manually",
1066
+ description: "Manually settle (claim) the swap on the destination smart chain",
1067
+ chain: this.chainIdentifier,
1068
+ txs: await this.prepareTransactions(this.txsClaim(actionOptions?.manualSettlementSmartChainSigner, actionOptions?.secret)),
1069
+ submitTransactions: async (txs: (T["SignedTXType"] | string)[], abortSignal?: AbortSignal, idempotent?: boolean) => {
1070
+ return this._submitExecutionTransactions(
1071
+ txs,
1072
+ abortSignal,
1073
+ [FromBTCLNAutoSwapState.CLAIM_COMMITED],
1074
+ idempotent
1075
+ );
1076
+ },
1077
+ requiredSigner: signerAddress ?? this._getInitiator()
1078
+ } as SwapExecutionActionSignSmartChainTx<T>;
1079
+ }
1080
+
1081
+ /**
1082
+ *
1083
+ * @param options.manualSettlementSmartChainSigner Optional smart chain signer to create a manual claim (settlement) transaction
1084
+ * @param options.maxWaitTillAutomaticSettlementSeconds Maximum time to wait for an automatic settlement after
1085
+ * the bitcoin transaction is confirmed (defaults to 60 seconds)
1086
+ * @param options.secret A swap secret to broadcast to watchtowers, generally only needed if the swap
1087
+ * was recovered from on-chain data, or the pre-image was generated outside the SDK
1088
+ */
1089
+ async getExecutionAction(options?: {
1090
+ manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
1091
+ maxWaitTillAutomaticSettlementSeconds?: number,
1092
+ secret?: string
1093
+ }): Promise<
1094
+ SwapExecutionActionSendToAddress<true> |
1095
+ SwapExecutionActionWait<"LP" | "SETTLEMENT"> |
1096
+ SwapExecutionActionSignSmartChainTx<T> |
1097
+ undefined
1098
+ > {
1099
+ const executionStatus = await this._getExecutionStatus(options);
1100
+ return executionStatus.buildCurrentAction(options);
1101
+ }
1102
+
1103
+ /**
1104
+ * @inheritDoc
1105
+ */
1106
+ // TODO: Figure how we gonna trigger an LNURL-withdraw with the execution actions
1107
+ async getExecutionStatus(options?: {
1108
+ skipBuildingAction?: boolean,
1109
+ manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
1110
+ maxWaitTillAutomaticSettlementSeconds?: number,
1111
+ secret?: string
1112
+ }): Promise<{
1113
+ steps: [
1114
+ SwapExecutionStepPayment<"LIGHTNING">,
1115
+ SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
1116
+ ],
1117
+ currentAction:
1118
+ SwapExecutionActionSendToAddress<true> |
1119
+ SwapExecutionActionWait<"LP" | "SETTLEMENT"> |
1120
+ SwapExecutionActionSignSmartChainTx<T> |
1121
+ undefined,
1122
+ stateInfo: SwapStateInfo<FromBTCLNAutoSwapState>
1123
+ }> {
1124
+ const executionStatus = await this._getExecutionStatus(options);
1125
+ return {
1126
+ steps: executionStatus.steps,
1127
+ currentAction: options?.skipBuildingAction ? undefined : await executionStatus.buildCurrentAction(options),
1128
+ stateInfo: this._getStateInfo(executionStatus.state)
1129
+ };
1130
+ }
1131
+
1132
+ /**
1133
+ * @inheritDoc
1134
+ */
1135
+ async getExecutionSteps(options?: {
1136
+ maxWaitTillAutomaticSettlementSeconds?: number
1137
+ }): Promise<[
1138
+ SwapExecutionStepPayment<"LIGHTNING">,
1139
+ SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
1140
+ ]> {
1141
+ return (await this._getExecutionStatus(options)).steps;
1142
+ }
1143
+
1144
+
1145
+ //////////////////////////////
1146
+ //// Payment
1147
+
1148
+ /**
1149
+ * Checks whether the LP received the LN payment
1150
+ *
1151
+ * @param save If the new swap state should be saved
1152
+ *
1153
+ * @internal
1154
+ */
1155
+ async _checkIntermediaryPaymentReceived(save: boolean = true): Promise<boolean | null> {
1156
+ if(
1157
+ this._state===FromBTCLNAutoSwapState.PR_PAID ||
1158
+ this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED ||
1159
+ this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED ||
1160
+ this._state===FromBTCLNAutoSwapState.FAILED ||
1161
+ this._state===FromBTCLNAutoSwapState.EXPIRED
1162
+ ) return true;
1163
+ if(this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED) return false;
1164
+ if(this.url==null) return false;
1165
+
1166
+ const paymentHash = this.getPaymentHash();
1167
+ if(paymentHash==null)
1168
+ throw new Error("Failed to check LP payment received, payment hash not known (probably recovered swap?)");
1169
+
1170
+ const resp = await this.wrapper._lpApi.getInvoiceStatus(this.url, paymentHash.toString("hex"));
1171
+ switch(resp.code) {
1172
+ case InvoiceStatusResponseCodes.PAID:
1173
+ const data = new (this.wrapper._swapDataDeserializer(this._contractVersion))(resp.data.data);
1174
+ if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) try {
1175
+ await this._saveRealSwapData(data, save);
1176
+ return true;
1177
+ } catch (e) {}
1178
+ return null;
1179
+ case InvoiceStatusResponseCodes.EXPIRED:
1180
+ this._state = FromBTCLNAutoSwapState.QUOTE_EXPIRED;
1181
+ this.initiated = true;
1182
+ if(save) await this._saveAndEmit();
1183
+ return false;
1184
+ default:
1185
+ return null;
1186
+ }
1187
+ }
1188
+
1189
+ /**
1190
+ * Checks and overrides the swap data for this swap. This is used to set the swap data from
1191
+ * on-chain events.
1192
+ *
1193
+ * @param data Swap data of the escrow swap
1194
+ * @param save If the new data should be saved
1195
+ *
1196
+ * @internal
1197
+ */
1198
+ async _saveRealSwapData(data: T["Data"], save?: boolean): Promise<boolean> {
1199
+ await this.checkIntermediaryReturnedData(data);
1200
+ if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) {
1201
+ this._state = FromBTCLNAutoSwapState.PR_PAID;
1202
+ this._data = data;
1203
+ this.initiated = true;
1204
+ if(save) await this._saveAndEmit();
1205
+ return true;
1206
+ }
1207
+ return false;
1208
+ }
1209
+
1210
+ /**
1211
+ * Waits till a lightning network payment is received by the intermediary, and the intermediary
1212
+ * initiates the swap HTLC on the smart chain side. After the HTLC is initiated you can wait
1213
+ * for an automatic settlement by the watchtowers with the {@link waitTillClaimed} function,
1214
+ * or settle manually using the {@link claim} or {@link txsClaim} functions.
1215
+ *
1216
+ * If this swap is using an LNURL-withdraw link as input, it automatically posts the
1217
+ * generated invoice to the LNURL service to pay it.
1218
+ *
1219
+ * @remarks For internal use, rather use {@link waitForPayment} which properly waits till the LP also
1220
+ * offers a swap HTLC.
1221
+ *
1222
+ * @param abortSignal Abort signal to stop waiting for payment
1223
+ * @param checkIntervalSeconds How often to poll the intermediary for answer (default 5 seconds)
1224
+ *
1225
+ * @internal
1226
+ */
1227
+ async _waitForLpPaymentReceived(checkIntervalSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
1228
+ checkIntervalSeconds ??= 5;
1229
+ if(this._state>=FromBTCLNAutoSwapState.PR_PAID) return true;
1230
+ if(
1231
+ this._state!==FromBTCLNAutoSwapState.PR_CREATED
1232
+ ) throw new Error("Must be in PR_CREATED state!");
1233
+
1234
+ const abortController = new AbortController();
1235
+ if(abortSignal!=null) abortSignal.addEventListener("abort", () => abortController.abort(abortSignal.reason));
1236
+
1237
+ let save = false;
1238
+
1239
+ if(this.lnurl!=null && this.lnurlK1!=null && this.lnurlCallback!=null && !this.prPosted) {
1240
+ if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
1241
+ throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
1242
+
1243
+ LNURL.postInvoiceToLNURLWithdraw({k1: this.lnurlK1, callback: this.lnurlCallback}, this.pr).catch(e => {
1244
+ this.lnurlFailSignal.abort(e);
1245
+ });
1246
+ this.prPosted = true;
1247
+ save ||= true;
1248
+ }
1249
+
1250
+ if(!this.initiated) {
1251
+ this.initiated = true;
1252
+ save ||= true;
1253
+ }
1254
+
1255
+ if(save) await this._saveAndEmit();
1256
+
1257
+ let lnurlFailListener = () => abortController.abort(this.lnurlFailSignal.signal.reason);
1258
+ this.lnurlFailSignal.signal.addEventListener("abort", lnurlFailListener);
1259
+ this.lnurlFailSignal.signal.throwIfAborted();
1260
+
1261
+ const paymentHash = this.getPaymentHash();
1262
+ if(paymentHash==null)
1263
+ throw new Error("Swap payment hash not available, the swap was probably recovered!");
1264
+
1265
+ if(this.wrapper._messenger.warmup!=null) await this.wrapper._messenger.warmup().catch(e => {
1266
+ this.logger.warn("waitForPayment(): Failed to warmup messenger: ", e);
1267
+ });
1268
+
1269
+ if(this._state===FromBTCLNAutoSwapState.PR_CREATED) {
1270
+ const promises: Promise<boolean | undefined>[] = [
1271
+ this.waitTillState(FromBTCLNAutoSwapState.PR_PAID, "gte", abortController.signal).then(() => true)
1272
+ ];
1273
+ if(this.url!=null) promises.push((async () => {
1274
+ let resp: InvoiceStatusResponse = {code: InvoiceStatusResponseCodes.PENDING, msg: ""};
1275
+ while(!abortController.signal.aborted && resp.code===InvoiceStatusResponseCodes.PENDING) {
1276
+ resp = await this.wrapper._lpApi.getInvoiceStatus(this.url!, paymentHash.toString("hex"));
1277
+ if(resp.code===InvoiceStatusResponseCodes.PENDING)
1278
+ await timeoutPromise(checkIntervalSeconds*1000, abortController.signal);
1279
+ }
1280
+ this.lnurlFailSignal.signal.removeEventListener("abort", lnurlFailListener);
1281
+ abortController.signal.throwIfAborted();
1282
+
1283
+ if(resp.code===InvoiceStatusResponseCodes.PAID) {
1284
+ const swapData = new (this.wrapper._swapDataDeserializer(this._contractVersion))(resp.data.data);
1285
+ return await this._saveRealSwapData(swapData, true);
1286
+ }
1287
+
1288
+ if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) {
1289
+ if(resp.code===InvoiceStatusResponseCodes.EXPIRED) {
1290
+ await this._saveAndEmit(FromBTCLNAutoSwapState.QUOTE_EXPIRED);
1291
+ }
1292
+ return false;
1293
+ }
1294
+ })());
1295
+ const paymentResult = await Promise.race(promises);
1296
+ abortController.abort();
1297
+
1298
+ if(!paymentResult) return false;
1299
+ }
1300
+
1301
+ return this._state>=FromBTCLNAutoSwapState.PR_PAID;
1302
+ }
1303
+
1304
+
1305
+ /**
1306
+ * Checks the data returned by the intermediary in the payment auth request
1307
+ *
1308
+ * @param data Parsed swap data as returned by the intermediary
1309
+ *
1310
+ * @throws {IntermediaryError} If the returned are not valid
1311
+ * @throws {Error} If the swap is already committed on-chain
1312
+ *
1313
+ * @private
1314
+ */
1315
+ private async checkIntermediaryReturnedData(data: T["Data"]): Promise<void> {
1316
+ if (!data.isPayOut()) throw new IntermediaryError("Invalid not pay out");
1317
+ if (data.getType() !== ChainSwapType.HTLC) throw new IntermediaryError("Invalid swap type");
1318
+ if (!data.isOfferer(this.getSwapData().getOfferer())) throw new IntermediaryError("Invalid offerer used");
1319
+ if (!data.isClaimer(this._getInitiator())) throw new IntermediaryError("Invalid claimer used");
1320
+ if (!data.isToken(this.getSwapData().getToken())) throw new IntermediaryError("Invalid token used");
1321
+ if (data.getSecurityDeposit() !== this.getSwapData().getSecurityDeposit()) throw new IntermediaryError("Invalid security deposit!");
1322
+ if (data.getClaimerBounty() !== this.getSwapData().getClaimerBounty()) throw new IntermediaryError("Invalid security deposit!");
1323
+ if (data.getAmount() < this.getSwapData().getAmount()) throw new IntermediaryError("Invalid amount received!");
1324
+ if (data.getClaimHash() !== this.getSwapData().getClaimHash()) throw new IntermediaryError("Invalid payment hash used!");
1325
+ if (!data.isDepositToken(this.getSwapData().getDepositToken())) throw new IntermediaryError("Invalid deposit token used!");
1326
+ if (data.hasSuccessAction()) throw new IntermediaryError("Invalid has success action");
1327
+
1328
+ if (await this.wrapper._contract(this._contractVersion).isExpired(this._getInitiator(), data)) throw new IntermediaryError("Not enough time to claim!");
1329
+ if (this.wrapper._getHtlcTimeout(data) <= (Date.now()/1000)) throw new IntermediaryError("HTLC expires too soon!");
1330
+ }
1331
+
1332
+ /**
1333
+ * Waits till a lightning network payment is received by the intermediary, and the intermediary
1334
+ * initiates the swap HTLC on the smart chain side. After the HTLC is initiated you can wait
1335
+ * for an automatic settlement by the watchtowers with the {@link waitTillClaimed} function,
1336
+ * or settle manually using the {@link claim} or {@link txsClaim} functions.
1337
+ *
1338
+ * If this swap is using an LNURL-withdraw link as input, it automatically posts the
1339
+ * generated invoice to the LNURL service to pay it.
1340
+ *
1341
+ * @param onPaymentReceived Callback as for when the LP reports having received the ln payment
1342
+ * @param abortSignal Abort signal to stop waiting for payment
1343
+ * @param checkIntervalSeconds How often to poll the intermediary for answer (default 5 seconds)
1344
+ */
1345
+ async waitForPayment(onPaymentReceived?: (txId: string) => void, checkIntervalSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
1346
+ checkIntervalSeconds ??= 5;
1347
+ if(this._state===FromBTCLNAutoSwapState.PR_PAID) {
1348
+ await this.waitTillCommited(checkIntervalSeconds, abortSignal);
1349
+ }
1350
+ if(this._state>=FromBTCLNAutoSwapState.CLAIM_COMMITED) return true;
1351
+
1352
+ const success = await this._waitForLpPaymentReceived(checkIntervalSeconds, abortSignal);
1353
+ if(!success) return false;
1354
+
1355
+ if(onPaymentReceived!=null) onPaymentReceived(this.getInputTxId()!);
1356
+
1357
+ if((this._state as FromBTCLNAutoSwapState)===FromBTCLNAutoSwapState.PR_PAID) {
1358
+ await this.waitTillCommited(checkIntervalSeconds, abortSignal);
1359
+ }
1360
+
1361
+ return this._state>=FromBTCLNAutoSwapState.CLAIM_COMMITED;
1362
+ }
1363
+
1364
+
1365
+ //////////////////////////////
1366
+ //// Commit
1367
+
1368
+ /**
1369
+ * Waits till the intermediary (LP) initiates the swap HTLC escrow on the destination smart chain side
1370
+ *
1371
+ * @param checkIntervalSeconds How often to check via a polling watchdog
1372
+ * @param abortSignal Abort signal
1373
+ *
1374
+ * @internal
1375
+ */
1376
+ protected async waitTillCommited(checkIntervalSeconds?: number, abortSignal?: AbortSignal): Promise<void> {
1377
+ if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED || this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED) return Promise.resolve();
1378
+ if(this._state!==FromBTCLNAutoSwapState.PR_PAID) throw new Error("Invalid state");
1379
+
1380
+ const abortController = extendAbortController(abortSignal);
1381
+ let result: SwapCommitState | number | null;
1382
+ try {
1383
+ result = await Promise.race([
1384
+ this.watchdogWaitTillCommited(checkIntervalSeconds, abortController.signal),
1385
+ this.waitTillState(FromBTCLNAutoSwapState.CLAIM_COMMITED, "gte", abortController.signal).then(() => 0)
1386
+ ]);
1387
+ abortController.abort();
1388
+ } catch (e) {
1389
+ abortController.abort();
1390
+ throw e;
1391
+ }
1392
+
1393
+ if(result===null) {
1394
+ this.logger.debug("waitTillCommited(): Resolved from watchdog - HTLC expired");
1395
+ if(
1396
+ this._state===FromBTCLNAutoSwapState.PR_PAID
1397
+ ) {
1398
+ await this._saveAndEmit(FromBTCLNAutoSwapState.EXPIRED);
1399
+ }
1400
+ return;
1401
+ }
1402
+
1403
+ if(
1404
+ this._state===FromBTCLNAutoSwapState.PR_PAID
1405
+ ) {
1406
+ if(typeof(result)==="object" && (result as any).getInitTxId!=null && this._commitTxId==null)
1407
+ this._commitTxId = await (result as any).getInitTxId();
1408
+ this._commitedAt ??= Date.now();
1409
+ await this._saveAndEmit(FromBTCLNAutoSwapState.CLAIM_COMMITED);
1410
+ }
1411
+
1412
+ if(result===0) {
1413
+ this.logger.debug("waitTillCommited(): Resolved from state changed");
1414
+ } else if(result!=null) {
1415
+ this.logger.debug("waitTillCommited(): Resolved from watchdog - commited");
1416
+ if(this.secret!=null) await this._broadcastSecret().catch(e => {
1417
+ this.logger.error("waitTillCommited(): Error broadcasting swap secret: ", e);
1418
+ });
1419
+ }
1420
+ }
1421
+
1422
+
1423
+ //////////////////////////////
1424
+ //// Claim
1425
+
1426
+ /**
1427
+ * @inheritDoc
1428
+ *
1429
+ * @param _signer Optional signer address to use for claiming the swap, can also be different from the initializer
1430
+ * @param secret A swap secret to use for the claim transaction, generally only needed if the swap
1431
+ * was recovered from on-chain data, or the pre-image was generated outside the SDK
1432
+ *
1433
+ * @throws {Error} If in invalid state (must be {@link FromBTCLNAutoSwapState.CLAIM_COMMITED})
1434
+ */
1435
+ async txsClaim(_signer?: string | T["Signer"] | T["NativeSigner"], secret?: string): Promise<T["TX"][]> {
1436
+ let address: string | undefined = undefined;
1437
+ if(_signer!=null) {
1438
+ if (typeof (_signer) === "string") {
1439
+ address = _signer;
1440
+ } else if (isAbstractSigner(_signer)) {
1441
+ address = _signer.getAddress();
1442
+ } else {
1443
+ address = (await this.wrapper._chain.wrapSigner(_signer)).getAddress();
1444
+ }
1445
+ }
1446
+ if(this._state!==FromBTCLNAutoSwapState.CLAIM_COMMITED) throw new Error("Must be in CLAIM_COMMITED state!");
1447
+ if(this._data==null) throw new Error("Unknown data, wrong state?");
1448
+
1449
+ const useSecret = secret ?? this.secret;
1450
+ if(useSecret==null)
1451
+ throw new Error("Swap secret pre-image not known and not provided, please provide the swap secret pre-image as an argument");
1452
+ if(!this.isValidSecretPreimage(useSecret))
1453
+ throw new Error("Invalid swap secret pre-image provided!");
1454
+
1455
+ return await this._contract.txsClaimWithSecret(
1456
+ address ?? this._getInitiator(),
1457
+ this._data, useSecret, true, true
1458
+ );
1459
+ }
1460
+
1461
+ /**
1462
+ * @inheritDoc
1463
+ *
1464
+ * @param _signer Signer to sign the transactions with, can also be different to the initializer
1465
+ * @param abortSignal Abort signal to stop waiting for transaction confirmation
1466
+ * @param onBeforeTxSent
1467
+ * @param secret A swap secret to use for the claim transaction, generally only needed if the swap
1468
+ * was recovered from on-chain data, or the pre-image was generated outside the SDK
1469
+ */
1470
+ async claim(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, onBeforeTxSent?: (txId: string) => void, secret?: string): Promise<string> {
1471
+ const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
1472
+ let txCount = 0;
1473
+ const txs = await this.txsClaim(_signer, secret);
1474
+ const result = await this.wrapper._chain.sendAndConfirm(
1475
+ signer, txs, true, abortSignal, undefined, (txId: string) => {
1476
+ txCount++;
1477
+ if(onBeforeTxSent!=null && txCount===1) onBeforeTxSent(txId);
1478
+ return Promise.resolve();
1479
+ }
1480
+ );
1481
+
1482
+ this._claimTxId = result[0];
1483
+ if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED || this._state===FromBTCLNAutoSwapState.EXPIRED || this._state===FromBTCLNAutoSwapState.FAILED) {
1484
+ await this._saveAndEmit(FromBTCLNAutoSwapState.CLAIM_CLAIMED);
1485
+ }
1486
+ return result[0];
1487
+ }
1488
+
1489
+ /**
1490
+ * Waits till the swap is successfully settled (claimed), should be called after sending the claim (settlement)
1491
+ * transactions manually to wait till the SDK processes the settlement and updates the swap state accordingly.
1492
+ *
1493
+ * @param maxWaitTimeSeconds Maximum time in seconds to wait for the swap to be settled
1494
+ * @param abortSignal AbortSignal
1495
+ * @param secret A swap secret to broadcast to watchtowers, generally only needed if the swap
1496
+ * was recovered from on-chain data, or the pre-image was generated outside the SDK
1497
+ * @param pollIntervalSeconds How often to poll via the watchdog
1498
+ *
1499
+ * @throws {Error} If swap is in invalid state (must be {@link FromBTCLNAutoSwapState.CLAIM_COMMITED})
1500
+ * @throws {Error} If the LP refunded sooner than we were able to claim
1501
+ * @returns {boolean} whether the swap was claimed in time or not
1502
+ */
1503
+ async waitTillClaimed(maxWaitTimeSeconds?: number, abortSignal?: AbortSignal, secret?: string, pollIntervalSeconds?: number): Promise<boolean> {
1504
+ if(this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED) return Promise.resolve(true);
1505
+ if(this._state!==FromBTCLNAutoSwapState.CLAIM_COMMITED) throw new Error("Invalid state (not CLAIM_COMMITED)");
1506
+
1507
+ if(secret!=null) {
1508
+ if(!this.isValidSecretPreimage(secret))
1509
+ throw new Error("Invalid swap secret pre-image provided!");
1510
+ this.secret = secret;
1511
+ }
1512
+
1513
+ const abortController = new AbortController();
1514
+ if(abortSignal!=null) abortSignal.addEventListener("abort", () => abortController.abort(abortSignal.reason));
1515
+ let timedOut: boolean = false;
1516
+ if(maxWaitTimeSeconds!=null) {
1517
+ const timeout = setTimeout(() => {
1518
+ timedOut = true;
1519
+ abortController.abort();
1520
+ }, maxWaitTimeSeconds * 1000);
1521
+ abortController.signal.addEventListener("abort", () => clearTimeout(timeout));
1522
+ }
1523
+
1524
+ let res: 0 | 1 | SwapCommitState;
1525
+ try {
1526
+ res = await Promise.race([
1527
+ this.watchdogWaitTillResult(pollIntervalSeconds, abortController.signal),
1528
+ this.waitTillState(FromBTCLNAutoSwapState.CLAIM_CLAIMED, "eq", abortController.signal).then(() => 0 as const),
1529
+ this.waitTillState(FromBTCLNAutoSwapState.EXPIRED, "eq", abortController.signal).then(() => 1 as const),
1530
+ ]);
1531
+ abortController.abort();
1532
+ } catch (e) {
1533
+ abortController.abort();
1534
+ if(timedOut) return false;
1535
+ throw e;
1536
+ }
1537
+
1538
+ if(res===0) {
1539
+ this.logger.debug("waitTillClaimed(): Resolved from state change (CLAIM_CLAIMED)");
1540
+ return true;
1541
+ }
1542
+ if(res===1) {
1543
+ this.logger.debug("waitTillClaimed(): Resolved from state change (EXPIRED)");
1544
+ throw new Error("Swap expired during claiming");
1545
+ }
1546
+ this.logger.debug("waitTillClaimed(): Resolved from watchdog");
1547
+
1548
+ if(res?.type===SwapCommitStateType.PAID) {
1549
+ if((this._state as FromBTCLNAutoSwapState)!==FromBTCLNAutoSwapState.CLAIM_CLAIMED) {
1550
+ this._claimTxId = await res.getClaimTxId();
1551
+ await this._saveAndEmit(FromBTCLNAutoSwapState.CLAIM_CLAIMED);
1552
+ }
1553
+ }
1554
+ if(res?.type===SwapCommitStateType.NOT_COMMITED || res?.type===SwapCommitStateType.EXPIRED) {
1555
+ if(
1556
+ (this._state as FromBTCLNAutoSwapState)!==FromBTCLNAutoSwapState.CLAIM_CLAIMED &&
1557
+ (this._state as FromBTCLNAutoSwapState)!==FromBTCLNAutoSwapState.FAILED
1558
+ ) {
1559
+ await this._saveAndEmit(FromBTCLNAutoSwapState.FAILED);
1560
+ }
1561
+ throw new Error("Swap expired during claiming");
1562
+ }
1563
+ return true;
1564
+ }
1565
+
1566
+
1567
+ //////////////////////////////
1568
+ //// LNURL
1569
+
1570
+ /**
1571
+ * Whether this swap uses an LNURL-withdraw link
1572
+ */
1573
+ isLNURL(): boolean {
1574
+ return this.lnurl!=null;
1575
+ }
1576
+
1577
+ /**
1578
+ * Gets the used LNURL or `null` if this is not an LNURL-withdraw swap
1579
+ */
1580
+ getLNURL(): string | null {
1581
+ return this.lnurl ?? null;
1582
+ }
1583
+
1584
+ /**
1585
+ * Pay the generated lightning network invoice with an LNURL-withdraw link, this
1586
+ * is useful when you want to display a lightning payment QR code and also want to
1587
+ * allow payments using LNURL-withdraw NFC cards.
1588
+ *
1589
+ * Note that the swap needs to be created **without** an LNURL to begin with for this function
1590
+ * to work. If this swap is already using an LNURL-withdraw link, this function throws.
1591
+ */
1592
+ async settleWithLNURLWithdraw(lnurl: string | LNURLWithdraw): Promise<void> {
1593
+ if(
1594
+ this._state!==FromBTCLNAutoSwapState.PR_CREATED &&
1595
+ this._state!==FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED
1596
+ ) throw new Error("Must be in PR_CREATED state!");
1597
+
1598
+ if(this.lnurl!=null) throw new Error("Cannot settle LNURL-withdraw swap with different LNURL");
1599
+ let lnurlParams: LNURLWithdrawParamsWithUrl;
1600
+ if(typeof(lnurl)==="string") {
1601
+ const parsedLNURL = await LNURL.getLNURL(lnurl);
1602
+ if(parsedLNURL==null || parsedLNURL.tag!=="withdrawRequest")
1603
+ throw new UserError("Invalid LNURL-withdraw to settle the swap");
1604
+ lnurlParams = parsedLNURL;
1605
+ } else {
1606
+ lnurlParams = lnurl.params;
1607
+ }
1608
+
1609
+ if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
1610
+ throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
1611
+
1612
+ LNURL.useLNURLWithdraw(lnurlParams, this.pr).catch(e => this.lnurlFailSignal.abort(e));
1613
+ this.lnurl = lnurlParams.url;
1614
+ this.lnurlCallback = lnurlParams.callback;
1615
+ this.lnurlK1 = lnurlParams.k1;
1616
+ this.prPosted = true;
1617
+ await this._saveAndEmit();
1618
+ }
1619
+
1620
+
1621
+ //////////////////////////////
1622
+ //// Storage
1623
+
1624
+ /**
1625
+ * @inheritDoc
1626
+ */
1627
+ serialize(): any {
1628
+ return {
1629
+ ...super.serialize(),
1630
+ data: this._data==null ? null : this._data.serialize(),
1631
+ commitTxId: this._commitTxId,
1632
+ claimTxId: this._claimTxId,
1633
+ commitedAt: this._commitedAt,
1634
+ btcAmountSwap: this.btcAmountSwap==null ? null : this.btcAmountSwap.toString(10),
1635
+ btcAmountGas: this.btcAmountGas==null ? null : this.btcAmountGas.toString(10),
1636
+ gasSwapFeeBtc: this.gasSwapFeeBtc==null ? null : this.gasSwapFeeBtc.toString(10),
1637
+ gasSwapFee: this.gasSwapFee==null ? null : this.gasSwapFee.toString(10),
1638
+ gasPricingInfo: serializePriceInfoType(this.gasPricingInfo),
1639
+ pr: this.pr,
1640
+ secret: this.secret,
1641
+ lnurl: this.lnurl,
1642
+ lnurlK1: this.lnurlK1,
1643
+ lnurlCallback: this.lnurlCallback,
1644
+ prPosted: this.prPosted,
1645
+ initialSwapData: this.initialSwapData.serialize(),
1646
+ usesClaimHashAsId: this.usesClaimHashAsId
1647
+ };
1648
+ }
1649
+
1650
+
1651
+ //////////////////////////////
1652
+ //// Swap ticks & sync
1653
+
1654
+ /**
1655
+ * Checks the swap's state on-chain and compares it to its internal state, updates/changes it according to on-chain
1656
+ * data
1657
+ *
1658
+ * @private
1659
+ */
1660
+ private async syncStateFromChain(quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState): Promise<boolean> {
1661
+ if(
1662
+ this._state===FromBTCLNAutoSwapState.PR_PAID ||
1663
+ this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED ||
1664
+ this._state===FromBTCLNAutoSwapState.EXPIRED
1665
+ ) {
1666
+ //Check for expiry before the getCommitStatus to prevent race conditions
1667
+ let quoteExpired: boolean = false;
1668
+ if(this._state===FromBTCLNAutoSwapState.PR_PAID) {
1669
+ quoteExpired = quoteDefinitelyExpired ?? await this._verifyQuoteDefinitelyExpired();
1670
+ }
1671
+
1672
+ //Check if it's already successfully paid
1673
+ commitStatus ??= await this._contract.getCommitStatus(this._getInitiator(), this._data!);
1674
+ if(commitStatus!=null && await this._forciblySetOnchainState(commitStatus)) return true;
1675
+
1676
+ if(this._state===FromBTCLNAutoSwapState.PR_PAID) {
1677
+ if(quoteExpired) {
1678
+ this._state = FromBTCLNAutoSwapState.QUOTE_EXPIRED;
1679
+ return true;
1680
+ }
1681
+ }
1682
+ }
1683
+
1684
+ return false;
1685
+ }
1686
+
1687
+ /**
1688
+ * @inheritDoc
1689
+ * @internal
1690
+ */
1691
+ _shouldFetchOnchainState(): boolean {
1692
+ return this._state===FromBTCLNAutoSwapState.PR_PAID || this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED || this._state===FromBTCLNAutoSwapState.EXPIRED;
1693
+ }
1694
+
1695
+ /**
1696
+ * @inheritDoc
1697
+ * @internal
1698
+ */
1699
+ _shouldFetchExpiryStatus(): boolean {
1700
+ return this._state===FromBTCLNAutoSwapState.PR_PAID;
1701
+ }
1702
+
1703
+ /**
1704
+ * @inheritDoc
1705
+ * @internal
1706
+ */
1707
+ _shouldCheckIntermediary(): boolean {
1708
+ return this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED;
1709
+ }
1710
+
1711
+ /**
1712
+ * @inheritDoc
1713
+ * @internal
1714
+ */
1715
+ async _sync(save?: boolean, quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState, skipLpCheck?: boolean): Promise<boolean> {
1716
+ let changed = false;
1717
+
1718
+ if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) {
1719
+ if(this._state!==FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED && this.getQuoteExpiry()<Date.now()) {
1720
+ this._state = FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED;
1721
+ changed ||= true;
1722
+ }
1723
+
1724
+ if(!skipLpCheck) try {
1725
+ const result = await this._checkIntermediaryPaymentReceived(false);
1726
+ if (result !== null) changed ||= true;
1727
+ } catch(e) {
1728
+ this.logger.error("_sync(): Failed to synchronize swap, error: ", e);
1729
+ }
1730
+
1731
+ if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) {
1732
+ if(await this._verifyQuoteDefinitelyExpired()) {
1733
+ this._state = FromBTCLNAutoSwapState.QUOTE_EXPIRED;
1734
+ changed ||= true;
1735
+ }
1736
+ }
1737
+ }
1738
+
1739
+ if(await this.syncStateFromChain(quoteDefinitelyExpired, commitStatus)) changed = true;
1740
+
1741
+ if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED) {
1742
+ const expired = await this._contract.isExpired(this._getInitiator(), this._data!);
1743
+ if(expired) {
1744
+ this._state = FromBTCLNAutoSwapState.EXPIRED;
1745
+ changed = true;
1746
+ }
1747
+ }
1748
+
1749
+ if(save && changed) await this._saveAndEmit();
1750
+
1751
+ if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED && this.secret!=null) await this._broadcastSecret().catch(e => {
1752
+ this.logger.error("_sync(): Error when broadcasting swap secret: ", e);
1753
+ });
1754
+
1755
+ return changed;
1756
+ }
1757
+
1758
+ /**
1759
+ * @inheritDoc
1760
+ * @internal
1761
+ */
1762
+ async _forciblySetOnchainState(commitStatus: SwapCommitState): Promise<boolean> {
1763
+ switch(commitStatus?.type) {
1764
+ case SwapCommitStateType.PAID:
1765
+ if(this._commitTxId==null && (commitStatus as any).getInitTxId!=null) this._commitTxId = await (commitStatus as any).getInitTxId();
1766
+ if(this._claimTxId==null) this._claimTxId = await commitStatus.getClaimTxId();
1767
+ if(this.secret==null || this.pr==null) this._setSwapSecret(await commitStatus.getClaimResult());
1768
+ this._state = FromBTCLNAutoSwapState.CLAIM_CLAIMED;
1769
+ return true;
1770
+ case SwapCommitStateType.NOT_COMMITED:
1771
+ let changed: boolean = false;
1772
+ if(this._commitTxId==null && (commitStatus as any).getInitTxId!=null) {
1773
+ this._commitTxId = await (commitStatus as any).getInitTxId();
1774
+ changed = true;
1775
+ }
1776
+ if(this._refundTxId==null && commitStatus.getRefundTxId!=null) {
1777
+ this._refundTxId = await commitStatus.getRefundTxId();
1778
+ changed = true;
1779
+ }
1780
+ if(this._refundTxId!=null) {
1781
+ this._state = FromBTCLNAutoSwapState.FAILED;
1782
+ changed = true;
1783
+ }
1784
+ return changed;
1785
+ case SwapCommitStateType.EXPIRED:
1786
+ if(this._commitTxId==null && (commitStatus as any).getInitTxId!=null) this._commitTxId = await (commitStatus as any).getInitTxId();
1787
+ if(this._refundTxId==null && commitStatus.getRefundTxId!=null) this._refundTxId = await commitStatus.getRefundTxId();
1788
+ this._state = this._refundTxId==null ? FromBTCLNAutoSwapState.QUOTE_EXPIRED : FromBTCLNAutoSwapState.FAILED;
1789
+ return true;
1790
+ case SwapCommitStateType.COMMITED:
1791
+ let save: boolean = false;
1792
+ if(this._commitTxId==null && (commitStatus as any).getInitTxId!=null) {
1793
+ this._commitTxId = await (commitStatus as any).getInitTxId();
1794
+ save = true;
1795
+ }
1796
+ if(this._state!==FromBTCLNAutoSwapState.CLAIM_COMMITED && this._state!==FromBTCLNAutoSwapState.EXPIRED) {
1797
+ this._commitedAt ??= Date.now();
1798
+ this._state = FromBTCLNAutoSwapState.CLAIM_COMMITED;
1799
+ save = true;
1800
+ }
1801
+ return save;
1802
+ }
1803
+ return false;
1804
+ }
1805
+
1806
+ /**
1807
+ * Broadcasts the swap secret to the underlying data propagation layer (e.g. Nostr by default)
1808
+ *
1809
+ * @param noCheckExpiry Whether a swap expiration check should be skipped broadcasting
1810
+ * @param secret An optional secret pre-image for the swap to broadcast
1811
+ *
1812
+ * @internal
1813
+ */
1814
+ async _broadcastSecret(noCheckExpiry?: boolean, secret?: string): Promise<void> {
1815
+ if(this._state!==FromBTCLNAutoSwapState.CLAIM_COMMITED) throw new Error("Must be in CLAIM_COMMITED state to broadcast swap secret!");
1816
+ if(this._data==null) throw new Error("Unknown data, wrong state?");
1817
+
1818
+ const useSecret = secret ?? this.secret;
1819
+ if(useSecret==null)
1820
+ throw new Error("Swap secret pre-image not known and not provided, please provide the swap secret pre-image as an argument");
1821
+ if(!this.isValidSecretPreimage(useSecret))
1822
+ throw new Error("Invalid swap secret pre-image provided!");
1823
+
1824
+ if(!noCheckExpiry) {
1825
+ if(await this._contract.isExpired(this._getInitiator(), this._data)) throw new Error("On-chain HTLC already expired!");
1826
+ }
1827
+ await this.wrapper._messenger.broadcast(new SwapClaimWitnessMessage(this._data, useSecret));
1828
+ }
1829
+
1830
+ /**
1831
+ * @inheritDoc
1832
+ * @internal
1833
+ */
1834
+ async _tick(save?: boolean): Promise<boolean> {
1835
+ switch(this._state) {
1836
+ case FromBTCLNAutoSwapState.PR_CREATED:
1837
+ if(this.getQuoteExpiry() < Date.now()) {
1838
+ this._state = FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED;
1839
+ if(save) await this._saveAndEmit();
1840
+ return true;
1841
+ }
1842
+ break;
1843
+ case FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED:
1844
+ if(this.getDefinitiveExpiryTime() < Date.now()) {
1845
+ this._state = FromBTCLNAutoSwapState.QUOTE_EXPIRED;
1846
+ if(save) await this._saveAndEmit();
1847
+ return true;
1848
+ }
1849
+ break;
1850
+ case FromBTCLNAutoSwapState.PR_PAID:
1851
+ case FromBTCLNAutoSwapState.CLAIM_COMMITED:
1852
+ const expired = await this._contract.isExpired(this._getInitiator(), this._data!);
1853
+ if(expired) {
1854
+ this._state = FromBTCLNAutoSwapState.EXPIRED;
1855
+ if(save) await this._saveAndEmit();
1856
+ return true;
1857
+ }
1858
+ if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED) {
1859
+ //Broadcast the secret over the provided messenger channel
1860
+ if(this.broadcastTickCounter===0 && this.secret!=null) await this._broadcastSecret(true).catch(e => {
1861
+ this.logger.warn("_tick(): Error when broadcasting swap secret: ", e);
1862
+ });
1863
+ this.broadcastTickCounter = (this.broadcastTickCounter + 1) % 3; //Broadcast every 3rd tick
1864
+ }
1865
+ break;
1866
+ }
1867
+
1868
+ return false;
1869
+ }
1870
+
1871
+ /**
1872
+ * Forcibly sets the swap secret pre-image from on-chain data
1873
+ *
1874
+ * @internal
1875
+ */
1876
+ _setSwapSecret(secret: string) {
1877
+ this.secret = secret;
1878
+ if(this.pr==null) {
1879
+ this.pr = Buffer.from(sha256(Buffer.from(secret, "hex"))).toString("hex");
1880
+ }
1881
+ }
1882
+
1883
+ }