@atomiqlabs/sdk 8.8.3 → 8.8.4

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