@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,1474 +1,1473 @@
1
- import {decode as bolt11Decode} from "@atomiqlabs/bolt11";
2
- import {FromBTCLNDefinition, FromBTCLNWrapper} from "./FromBTCLNWrapper";
3
- import {IFromBTCSelfInitSwap} from "../IFromBTCSelfInitSwap";
4
- import {SwapType} from "../../../../enums/SwapType";
5
- import {
6
- ChainSwapType,
7
- ChainType, isAbstractSigner,
8
- SignatureData,
9
- SwapCommitState,
10
- SwapCommitStateType,
11
- SwapData,
12
- SignatureVerificationError
13
- } from "@atomiqlabs/base";
14
- import {Buffer} from "buffer";
15
- import {LNURL} from "../../../../lnurl/LNURL";
16
- import {UserError} from "../../../../errors/UserError";
17
- import {
18
- IntermediaryAPI,
19
- PaymentAuthorizationResponse,
20
- PaymentAuthorizationResponseCodes
21
- } from "../../../../intermediaries/apis/IntermediaryAPI";
22
- import {IntermediaryError} from "../../../../errors/IntermediaryError";
23
- import {extendAbortController} from "../../../../utils/Utils";
24
- import {MinimalLightningNetworkWalletInterface} from "../../../../types/wallets/MinimalLightningNetworkWalletInterface";
25
- import {IClaimableSwap} from "../../../IClaimableSwap";
26
- import {IAddressSwap} from "../../../IAddressSwap";
27
- import {IEscrowSelfInitSwapInit, isIEscrowSelfInitSwapInit} from "../../IEscrowSelfInitSwap";
28
- import {TokenAmount, toTokenAmount} from "../../../../types/TokenAmount";
29
- import {BitcoinTokens, BtcToken, SCToken} from "../../../../types/Token";
30
- import {getLogger, LoggerType} from "../../../../utils/Logger";
31
- import {timeoutPromise} from "../../../../utils/TimeoutUtils";
32
- import {isLNURLWithdraw, LNURLWithdraw, LNURLWithdrawParamsWithUrl} from "../../../../types/lnurl/LNURLWithdraw";
33
- import {sha256} from "@noble/hashes/sha2";
34
- import {SwapExecutionAction} from "../../../../types/SwapExecutionAction";
35
-
36
- /**
37
- * State enum for legacy Lightning -> Smart chain swaps
38
- * @category Swaps/Legacy/Lightning → Smart chain
39
- */
40
- export enum FromBTCLNSwapState {
41
- /**
42
- * Swap has failed as the user didn't settle the HTLC on the destination before expiration
43
- */
44
- FAILED = -4,
45
- /**
46
- * Swap has expired for good and there is no way how it can be executed anymore
47
- */
48
- QUOTE_EXPIRED = -3,
49
- /**
50
- * A swap is almost expired, and it should be presented to the user as expired, though
51
- * there is still a chance that it will be processed
52
- */
53
- QUOTE_SOFT_EXPIRED = -2,
54
- /**
55
- * Swap HTLC on the destination chain has expired, it is not safe anymore to settle (claim) the
56
- * swap on the destination smart chain.
57
- */
58
- EXPIRED = -1,
59
- /**
60
- * Swap quote was created, use {@link FromBTCLNSwap.getAddress} or {@link FromBTCLNSwap.getHyperlink}
61
- * to get the bolt11 lightning network invoice to pay to initiate the swap, then use the
62
- * {@link FromBTCLNSwap.waitForPayment} to wait till the lightning network payment is received
63
- * by the intermediary (LP)
64
- */
65
- PR_CREATED = 0,
66
- /**
67
- * Lightning network payment has been received by the intermediary (LP), the user can now settle
68
- * the swap on the destination smart chain side with {@link FromBTCLNSwap.commitAndClaim} (if
69
- * the underlying chain supports it - check with {@link FromBTCLNSwap.canCommitAndClaimInOneShot}),
70
- * or by calling {@link FromBTCLNSwap.commit} and {@link FromBTCLNSwap.claim} separately.
71
- */
72
- PR_PAID = 1,
73
- /**
74
- * Swap escrow HTLC has been created on the destination chain. Continue by claiming it with the
75
- * {@link FromBTCLNSwap.claim} or {@link FromBTCLNSwap.txsClaim} function.
76
- */
77
- CLAIM_COMMITED = 2,
78
- /**
79
- * Swap successfully settled and funds received on the destination chain
80
- */
81
- CLAIM_CLAIMED = 3
82
- }
83
-
84
- const FromBTCLNSwapStateDescription = {
85
- [FromBTCLNSwapState.FAILED]: "Swap has failed as the user didn't settle the HTLC on the destination before expiration",
86
- [FromBTCLNSwapState.QUOTE_EXPIRED]: "Swap has expired for good and there is no way how it can be executed anymore",
87
- [FromBTCLNSwapState.QUOTE_SOFT_EXPIRED]: "Swap is expired, though there is still a chance that it will be processed",
88
- [FromBTCLNSwapState.EXPIRED]: "Swap HTLC on the destination chain has expired, it is not safe anymore to settle (claim) the swap on the destination smart chain.",
89
- [FromBTCLNSwapState.PR_CREATED]: "Swap quote was created, pay the bolt11 lightning network invoice to initiate the swap, then use the wait till the lightning network payment is received by the intermediary (LP)",
90
- [FromBTCLNSwapState.PR_PAID]: "Lightning network payment has been received by the intermediary (LP), the user can now settle the swap on the destination smart chain side.",
91
- [FromBTCLNSwapState.CLAIM_COMMITED]: "Swap escrow HTLC has been created on the destination chain. Continue by claiming it.",
92
- [FromBTCLNSwapState.CLAIM_CLAIMED]: "Swap successfully settled and funds received on the destination chain"
93
- };
94
-
95
- export type FromBTCLNSwapInit<T extends SwapData> = IEscrowSelfInitSwapInit<T> & {
96
- pr?: string,
97
- secret?: string,
98
- initialSwapData: T,
99
- lnurl?: string,
100
- lnurlK1?: string,
101
- lnurlCallback?: string
102
- };
103
-
104
- export function isFromBTCLNSwapInit<T extends SwapData>(obj: any): obj is FromBTCLNSwapInit<T> {
105
- return (obj.pr==null || typeof obj.pr==="string") &&
106
- (obj.secret==null || typeof obj.secret==="string") &&
107
- (obj.lnurl==null || typeof(obj.lnurl)==="string") &&
108
- (obj.lnurlK1==null || typeof(obj.lnurlK1)==="string") &&
109
- (obj.lnurlCallback==null || typeof(obj.lnurlCallback)==="string") &&
110
- isIEscrowSelfInitSwapInit(obj);
111
- }
112
-
113
- /**
114
- * Legacy escrow (HTLC) based swap for Bitcoin Lightning -> Smart chains, requires manual settlement
115
- * of the swap on the destination network once the lightning network payment is received by the LP.
116
- *
117
- * @category Swaps/Legacy/Lightning → Smart chain
118
- */
119
- export class FromBTCLNSwap<T extends ChainType = ChainType>
120
- extends IFromBTCSelfInitSwap<T, FromBTCLNDefinition<T>, FromBTCLNSwapState>
121
- implements IAddressSwap, IClaimableSwap<T, FromBTCLNDefinition<T>, FromBTCLNSwapState> {
122
-
123
- protected readonly TYPE = SwapType.FROM_BTCLN;
124
- /**
125
- * @internal
126
- */
127
- protected readonly swapStateName = (state: number) => FromBTCLNSwapState[state];
128
- /**
129
- * @internal
130
- */
131
- protected readonly swapStateDescription = FromBTCLNSwapStateDescription;
132
- /**
133
- * @internal
134
- */
135
- protected readonly logger: LoggerType;
136
- /**
137
- * @internal
138
- */
139
- protected readonly inputToken: BtcToken<true> = BitcoinTokens.BTCLN;
140
-
141
- private readonly lnurlFailSignal: AbortController = new AbortController();
142
- private readonly usesClaimHashAsId: boolean;
143
- private readonly initialSwapData: T["Data"];
144
-
145
- /**
146
- * In case the swap is recovered from on-chain data, the pr saved here is just a payment hash,
147
- * as it is impossible to retrieve the actual lightning network invoice paid purely from on-chain
148
- * data
149
- * @private
150
- */
151
- private pr?: string;
152
- private secret?: string;
153
-
154
- private lnurl?: string;
155
- private lnurlK1?: string;
156
- private lnurlCallback?: string;
157
- private prPosted?: boolean = false;
158
-
159
- /**
160
- * Sets the LNURL data for the swap
161
- *
162
- * @internal
163
- */
164
- _setLNURLData(lnurl: string, lnurlK1: string, lnurlCallback: string) {
165
- this.lnurl = lnurl;
166
- this.lnurlK1 = lnurlK1;
167
- this.lnurlCallback = lnurlCallback;
168
- }
169
-
170
- constructor(wrapper: FromBTCLNWrapper<T>, init: FromBTCLNSwapInit<T["Data"]>);
171
- constructor(wrapper: FromBTCLNWrapper<T>, obj: any);
172
- constructor(
173
- wrapper: FromBTCLNWrapper<T>,
174
- initOrObject: FromBTCLNSwapInit<T["Data"]> | any
175
- ) {
176
- if(isFromBTCLNSwapInit(initOrObject) && initOrObject.url!=null) initOrObject.url += "/frombtcln";
177
- super(wrapper, initOrObject);
178
- if(isFromBTCLNSwapInit(initOrObject)) {
179
- this._state = FromBTCLNSwapState.PR_CREATED;
180
- this.pr = initOrObject.pr;
181
- this.secret = initOrObject.secret;
182
- this.initialSwapData = initOrObject.initialSwapData;
183
- this.lnurl = initOrObject.lnurl;
184
- this.lnurlK1 = initOrObject.lnurlK1;
185
- this.lnurlCallback = initOrObject.lnurlCallback;
186
- this.usesClaimHashAsId = true;
187
- } else {
188
- this.pr = initOrObject.pr;
189
- this.secret = initOrObject.secret;
190
-
191
- if(initOrObject.initialSwapData==null) {
192
- this.initialSwapData = this._data!;
193
- } else {
194
- this.initialSwapData = SwapData.deserialize<T["Data"]>(initOrObject.initialSwapData);
195
- }
196
-
197
- this.lnurl = initOrObject.lnurl;
198
- this.lnurlK1 = initOrObject.lnurlK1;
199
- this.lnurlCallback = initOrObject.lnurlCallback;
200
- this.prPosted = initOrObject.prPosted;
201
-
202
- if(this._state===FromBTCLNSwapState.PR_CREATED && this._data!=null) {
203
- this.initialSwapData = this._data;
204
- delete this._data;
205
- }
206
- this.usesClaimHashAsId = initOrObject.usesClaimHashAsId ?? false;
207
- }
208
- this.tryRecomputeSwapPrice();
209
- this.logger = getLogger("FromBTCLN("+this.getIdentifierHashString()+"): ");
210
- }
211
-
212
- /**
213
- * @inheritDoc
214
- * @internal
215
- */
216
- protected getSwapData(): T["Data"] {
217
- return this._data ?? this.initialSwapData;
218
- }
219
-
220
- /**
221
- * @inheritDoc
222
- * @internal
223
- */
224
- protected upgradeVersion() {
225
- if (this.version == null) {
226
- switch (this._state) {
227
- case -2:
228
- this._state = FromBTCLNSwapState.QUOTE_EXPIRED;
229
- break;
230
- case -1:
231
- this._state = FromBTCLNSwapState.FAILED;
232
- break;
233
- case 0:
234
- this._state = FromBTCLNSwapState.PR_CREATED
235
- break;
236
- case 1:
237
- this._state = FromBTCLNSwapState.PR_PAID
238
- break;
239
- case 2:
240
- this._state = FromBTCLNSwapState.CLAIM_COMMITED
241
- break;
242
- case 3:
243
- this._state = FromBTCLNSwapState.CLAIM_CLAIMED
244
- break;
245
- }
246
- this.version = 1;
247
- }
248
- }
249
-
250
- //////////////////////////////
251
- //// Getters & utils
252
-
253
- /**
254
- * @inheritDoc
255
- * @internal
256
- */
257
- protected getIdentifierHash(): Buffer {
258
- const idBuffer: Buffer = this.usesClaimHashAsId
259
- ? Buffer.from(this.getClaimHash(), "hex")
260
- : this.getPaymentHash()!;
261
- if(this._randomNonce==null) return idBuffer;
262
- return Buffer.concat([idBuffer, Buffer.from(this._randomNonce, "hex")]);
263
- }
264
-
265
- /**
266
- * Returns the payment hash of the swap and lightning network invoice, or `null` if not known (i.e. if
267
- * the swap was recovered from on-chain data, the payment hash might not be known)
268
- *
269
- * @internal
270
- */
271
- protected getPaymentHash(): Buffer | null {
272
- if(this.pr==null) return null;
273
- if(this.pr.toLowerCase().startsWith("ln")) {
274
- const parsed = bolt11Decode(this.pr);
275
- if(parsed.tagsObject.payment_hash==null) throw new Error("Swap invoice has no payment hash field!");
276
- return Buffer.from(parsed.tagsObject.payment_hash, "hex");
277
- }
278
- return Buffer.from(this.pr, "hex");
279
- }
280
-
281
- /**
282
- * @inheritDoc
283
- * @internal
284
- */
285
- protected canCommit(skipQuoteExpiryChecks?: boolean): boolean {
286
- return this._state===FromBTCLNSwapState.PR_PAID || (!!skipQuoteExpiryChecks && this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED);
287
- }
288
-
289
- /**
290
- * @inheritDoc
291
- */
292
- getInputAddress(): string | null {
293
- return this.lnurl ?? this.pr ?? null;
294
- }
295
-
296
- /**
297
- * @inheritDoc
298
- */
299
- getInputTxId(): string | null {
300
- const paymentHash = this.getPaymentHash();
301
- if(paymentHash==null) return null;
302
- return paymentHash.toString("hex");
303
- }
304
-
305
- /**
306
- * Returns the lightning network BOLT11 invoice that needs to be paid as an input to the swap.
307
- *
308
- * In case the swap is recovered from on-chain data, the address returned might be just a payment hash,
309
- * as it is impossible to retrieve the actual lightning network invoice paid purely from on-chain
310
- * data.
311
- */
312
- getAddress(): string {
313
- return this.pr ?? "";
314
- }
315
-
316
- /**
317
- * A hyperlink representation of the address + amount that the user needs to sends on the source chain.
318
- * This is suitable to be displayed in a form of QR code.
319
- *
320
- * @remarks
321
- * In case the swap is recovered from on-chain data, the address returned might be just a payment hash,
322
- * as it is impossible to retrieve the actual lightning network invoice paid purely from on-chain
323
- * data.
324
- */
325
- getHyperlink(): string {
326
- return this.pr==null ? "" : "lightning:"+this.pr.toUpperCase();
327
- }
328
-
329
- /**
330
- * Returns the timeout time (in UNIX milliseconds) when the swap will definitelly be considered as expired
331
- * if the LP doesn't make it expired sooner
332
- */
333
- getDefinitiveExpiryTime(): number {
334
- if(this.pr==null || !this.pr.toLowerCase().startsWith("ln")) return 0;
335
- const decoded = bolt11Decode(this.pr);
336
- if(decoded.timeExpireDate==null) throw new Error("Swap invoice doesn't contain expiry date field!");
337
- const finalCltvExpiryDelta = decoded.tagsObject.min_final_cltv_expiry ?? 144;
338
- const finalCltvExpiryDelay = finalCltvExpiryDelta * this.wrapper._options.bitcoinBlocktime * this.wrapper._options.safetyFactor;
339
- return (decoded.timeExpireDate + finalCltvExpiryDelay)*1000;
340
- }
341
-
342
- /**
343
- * Returns timeout time (in UNIX milliseconds) when the swap htlc will expire
344
- */
345
- getHtlcTimeoutTime(): number | null {
346
- if(this._data==null) return null;
347
- return Number(this.wrapper._getHtlcTimeout(this._data))*1000;
348
- }
349
-
350
- /**
351
- * Returns timeout time (in UNIX milliseconds) when the LN invoice will expire
352
- */
353
- getTimeoutTime(): number {
354
- if(this.pr==null || !this.pr.toLowerCase().startsWith("ln")) return 0;
355
- const decoded = bolt11Decode(this.pr);
356
- if(decoded.timeExpireDate==null) throw new Error("Swap invoice doesn't contain expiry date field!");
357
- return (decoded.timeExpireDate*1000);
358
- }
359
-
360
- /**
361
- * @inheritDoc
362
- */
363
- isFinished(): boolean {
364
- return this._state===FromBTCLNSwapState.CLAIM_CLAIMED || this._state===FromBTCLNSwapState.QUOTE_EXPIRED || this._state===FromBTCLNSwapState.FAILED;
365
- }
366
-
367
- /**
368
- * @inheritDoc
369
- */
370
- isClaimable(): boolean {
371
- return this._state===FromBTCLNSwapState.CLAIM_COMMITED;
372
- }
373
-
374
- /**
375
- * @inheritDoc
376
- */
377
- isSuccessful(): boolean {
378
- return this._state===FromBTCLNSwapState.CLAIM_CLAIMED;
379
- }
380
-
381
- /**
382
- * @inheritDoc
383
- */
384
- isFailed(): boolean {
385
- return this._state===FromBTCLNSwapState.FAILED || this._state===FromBTCLNSwapState.EXPIRED;
386
- }
387
-
388
- /**
389
- * @inheritDoc
390
- */
391
- isInProgress(): boolean {
392
- return (this._state===FromBTCLNSwapState.PR_CREATED && this.initiated) ||
393
- (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.initiated) ||
394
- this._state===FromBTCLNSwapState.PR_PAID ||
395
- this._state===FromBTCLNSwapState.CLAIM_COMMITED;
396
- }
397
-
398
- /**
399
- * @inheritDoc
400
- */
401
- isQuoteExpired(): boolean {
402
- return this._state===FromBTCLNSwapState.QUOTE_EXPIRED;
403
- }
404
-
405
- /**
406
- * @inheritDoc
407
- */
408
- isQuoteSoftExpired(): boolean {
409
- return this._state===FromBTCLNSwapState.QUOTE_EXPIRED || this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED;
410
- }
411
-
412
- /**
413
- * @inheritDoc
414
- * @internal
415
- */
416
- _verifyQuoteDefinitelyExpired(): Promise<boolean> {
417
- if(this._state===FromBTCLNSwapState.PR_CREATED || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData==null)) {
418
- return Promise.resolve(this.getDefinitiveExpiryTime()<Date.now());
419
- }
420
- return super._verifyQuoteDefinitelyExpired();
421
- }
422
-
423
- /**
424
- * @inheritDoc
425
- * @internal
426
- */
427
- _verifyQuoteValid(): Promise<boolean> {
428
- if(
429
- this._state===FromBTCLNSwapState.PR_CREATED ||
430
- (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData==null)
431
- ) {
432
- return Promise.resolve(this.getTimeoutTime()>Date.now());
433
- }
434
- return super._verifyQuoteValid();
435
- }
436
-
437
-
438
- //////////////////////////////
439
- //// Amounts & fees
440
-
441
- /**
442
- * @inheritDoc
443
- */
444
- getInputToken(): BtcToken<true> {
445
- return BitcoinTokens.BTCLN;
446
- }
447
-
448
- /**
449
- * @inheritDoc
450
- */
451
- getInput(): TokenAmount<BtcToken<true>> {
452
- if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
453
- return toTokenAmount(null, this.inputToken, this.wrapper._prices, this.pricingInfo);
454
-
455
- const parsed = bolt11Decode(this.pr);
456
- if(parsed.millisatoshis==null) throw new Error("Swap invoice doesn't contain msat amount field!");
457
- const amount = (BigInt(parsed.millisatoshis) + 999n) / 1000n;
458
- return toTokenAmount(amount, this.inputToken, this.wrapper._prices, this.pricingInfo);
459
- }
460
-
461
- /**
462
- * @inheritDoc
463
- */
464
- getSmartChainNetworkFee(): Promise<TokenAmount<SCToken<T["ChainId"]>, true>> {
465
- return this.getCommitAndClaimNetworkFee();
466
- }
467
-
468
- /**
469
- * @inheritDoc
470
- */
471
- async hasEnoughForTxFees(): Promise<{
472
- enoughBalance: boolean,
473
- balance: TokenAmount<SCToken<T["ChainId"]>, true>,
474
- required: TokenAmount<SCToken<T["ChainId"]>, true>
475
- }> {
476
- const [balance, feeRate] = await Promise.all([
477
- this._contract.getBalance(this._getInitiator(), this.wrapper._chain.getNativeCurrencyAddress(), false),
478
- this.feeRate!=null ? Promise.resolve<string>(this.feeRate) : this._contract.getInitFeeRate(
479
- this.getSwapData().getOfferer(),
480
- this.getSwapData().getClaimer(),
481
- this.getSwapData().getToken(),
482
- this.getSwapData().getClaimHash()
483
- )
484
- ]);
485
- const commitFee = await this._contract.getCommitFee(this._getInitiator(), this.getSwapData(), feeRate);
486
- const claimFee = await this._contract.getClaimFee(this._getInitiator(), this.getSwapData(), feeRate);
487
- const totalFee = commitFee + claimFee + this.getSwapData().getTotalDeposit();
488
- return {
489
- enoughBalance: balance >= totalFee,
490
- balance: toTokenAmount(balance, this.wrapper._getNativeToken(), this.wrapper._prices, this.pricingInfo),
491
- required: toTokenAmount(totalFee, this.wrapper._getNativeToken(), this.wrapper._prices, this.pricingInfo)
492
- };
493
- }
494
-
495
- private isValidSecretPreimage(secret: string) {
496
- const paymentHash = Buffer.from(sha256(Buffer.from(secret, "hex")));
497
- const claimHash = this._contract.getHashForHtlc(paymentHash).toString("hex");
498
- return this.getSwapData().getClaimHash()===claimHash;
499
- }
500
-
501
- /**
502
- * Sets the secret preimage for the swap, in case it is not known already
503
- *
504
- * @param secret Secret preimage that matches the expected payment hash
505
- *
506
- * @throws {Error} If an invalid secret preimage is provided
507
- */
508
- setSecretPreimage(secret: string) {
509
- if(!this.isValidSecretPreimage(secret)) throw new Error("Invalid secret preimage provided, hash doesn't match!");
510
- this.secret = secret;
511
- }
512
-
513
- /**
514
- * Returns whether the secret preimage for this swap is known
515
- */
516
- hasSecretPreimage(): boolean {
517
- return this.secret != null;
518
- }
519
-
520
-
521
- //////////////////////////////
522
- //// Execution
523
-
524
- /**
525
- * Executes the swap with the provided bitcoin lightning network wallet or LNURL
526
- *
527
- * @param dstSigner Signer on the destination network, needs to have the same address as the one specified when
528
- * quote was created, this is required for legacy swaps because the destination wallet needs to actively claim
529
- * the swap funds on the destination (this also means you need native token to cover gas costs)
530
- * @param walletOrLnurlWithdraw Bitcoin lightning wallet to use to pay the lightning network invoice, or an LNURL-withdraw
531
- * link, wallet is not required and the LN invoice can be paid externally as well (just pass null or undefined here)
532
- * @param callbacks Callbacks to track the progress of the swap
533
- * @param options Optional options for the swap like feeRate, AbortSignal, and timeouts/intervals
534
- * @param options.secret A swap secret to use for the claim transaction, generally only needed if the swap
535
- * was recovered from on-chain data, or the pre-image was generated outside the SDK
536
- */
537
- async execute(
538
- dstSigner: T["Signer"] | T["NativeSigner"],
539
- walletOrLnurlWithdraw?: MinimalLightningNetworkWalletInterface | LNURLWithdraw | string | null | undefined,
540
- callbacks?: {
541
- onSourceTransactionReceived?: (sourceTxId: string) => void,
542
- onDestinationCommitSent?: (destinationCommitTxId: string) => void,
543
- onDestinationClaimSent?: (destinationClaimTxId: string) => void,
544
- onSwapSettled?: (destinationTxId: string) => void
545
- },
546
- options?: {
547
- abortSignal?: AbortSignal,
548
- secret?: string,
549
- lightningTxCheckIntervalSeconds?: number,
550
- delayBetweenCommitAndClaimSeconds?: number
551
- }
552
- ): Promise<boolean> {
553
- if(this._state===FromBTCLNSwapState.FAILED) throw new Error("Swap failed!");
554
- if(this._state===FromBTCLNSwapState.EXPIRED) throw new Error("Swap HTLC expired!");
555
- if(this._state===FromBTCLNSwapState.QUOTE_EXPIRED || this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED) throw new Error("Swap quote expired!");
556
- if(this._state===FromBTCLNSwapState.CLAIM_CLAIMED) throw new Error("Swap already settled!");
557
-
558
- let abortSignal = options?.abortSignal;
559
-
560
- if(this._state===FromBTCLNSwapState.PR_CREATED) {
561
- if(walletOrLnurlWithdraw!=null && this.lnurl==null) {
562
- if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
563
- throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
564
-
565
- if(typeof(walletOrLnurlWithdraw)==="string" || isLNURLWithdraw(walletOrLnurlWithdraw)) {
566
- await this.settleWithLNURLWithdraw(walletOrLnurlWithdraw);
567
- } else {
568
- const paymentPromise = walletOrLnurlWithdraw.payInvoice(this.pr);
569
-
570
- const abortController = new AbortController();
571
- paymentPromise.catch(e => abortController.abort(e));
572
- if(options?.abortSignal!=null) options.abortSignal.addEventListener("abort", () => abortController.abort(options?.abortSignal?.reason));
573
- abortSignal = abortController.signal;
574
- }
575
- }
576
- const paymentSuccess = await this.waitForPayment(callbacks?.onSourceTransactionReceived, options?.lightningTxCheckIntervalSeconds, abortSignal);
577
- if (!paymentSuccess) throw new Error("Failed to receive lightning network payment");
578
- }
579
-
580
- if(this._state===FromBTCLNSwapState.PR_PAID || this._state===FromBTCLNSwapState.CLAIM_COMMITED) {
581
- if(this.canCommitAndClaimInOneShot()) {
582
- await this.commitAndClaim(dstSigner, options?.abortSignal, undefined, callbacks?.onDestinationCommitSent, callbacks?.onDestinationClaimSent, options?.secret);
583
- } else {
584
- if(this._state===FromBTCLNSwapState.PR_PAID) {
585
- await this.commit(dstSigner, options?.abortSignal, undefined, callbacks?.onDestinationCommitSent);
586
- if(options?.delayBetweenCommitAndClaimSeconds!=null) await timeoutPromise(options.delayBetweenCommitAndClaimSeconds * 1000, options?.abortSignal);
587
- }
588
- if(this._state===FromBTCLNSwapState.CLAIM_COMMITED) {
589
- await this.claim(dstSigner, options?.abortSignal, callbacks?.onDestinationClaimSent, options?.secret);
590
- }
591
- }
592
- }
593
-
594
- // @ts-ignore
595
- if(this._state===FromBTCLNSwapState.CLAIM_CLAIMED) {
596
- if(callbacks?.onSwapSettled!=null) callbacks.onSwapSettled(this.getOutputTxId()!);
597
- }
598
-
599
- return true;
600
- }
601
-
602
- /**
603
- * @inheritDoc
604
- *
605
- * @param options
606
- * @param options.skipChecks Skip checks like making sure init signature is still valid and swap
607
- * wasn't commited yet (this is handled on swap creation, if you commit right after quoting, you
608
- * can use `skipChecks=true`)
609
- * @param options.secret A swap secret to use for the claim transaction, generally only needed if the swap
610
- * was recovered from on-chain data, or the pre-image was generated outside the SDK
611
- */
612
- async txsExecute(options?: {
613
- skipChecks?: boolean,
614
- secret?: string
615
- }) {
616
- if(this._state===FromBTCLNSwapState.PR_CREATED) {
617
- if(!await this._verifyQuoteValid()) throw new Error("Quote already expired or close to expiry!");
618
- return [
619
- {
620
- name: "Payment" as const,
621
- description: "Initiates the swap by paying up the lightning network invoice",
622
- chain: "LIGHTNING" as const,
623
- txs: [
624
- {
625
- type: "BOLT11_PAYMENT_REQUEST" as const,
626
- address: this.getAddress(),
627
- hyperlink: this.getHyperlink()
628
- }
629
- ]
630
- }
631
- ];
632
- }
633
-
634
- if(this._state===FromBTCLNSwapState.PR_PAID) {
635
- if(!await this._verifyQuoteValid()) throw new Error("Quote already expired or close to expiry!");
636
- const txsCommit = await this.txsCommit(options?.skipChecks);
637
- const txsClaim = await this._txsClaim(undefined, options?.secret);
638
- return [
639
- {
640
- name: "Commit" as const,
641
- description: `Creates the HTLC escrow on the ${this.chainIdentifier} side`,
642
- chain: this.chainIdentifier,
643
- txs: txsCommit
644
- },
645
- {
646
- name: "Claim" as const,
647
- description: `Settles & claims the funds from the HTLC escrow on the ${this.chainIdentifier} side`,
648
- chain: this.chainIdentifier,
649
- txs: txsClaim
650
- },
651
- ];
652
- }
653
-
654
- if(this._state===FromBTCLNSwapState.CLAIM_COMMITED) {
655
- const txsClaim = await this.txsClaim(undefined, options?.secret);
656
- return [
657
- {
658
- name: "Claim" as const,
659
- description: `Settles & claims the funds from the HTLC escrow on the ${this.chainIdentifier} side`,
660
- chain: this.chainIdentifier,
661
- txs: txsClaim
662
- },
663
- ];
664
- }
665
-
666
- throw new Error("Invalid swap state to obtain execution txns, required PR_CREATED, PR_PAID or CLAIM_COMMITED");
667
- }
668
-
669
- /**
670
- * @inheritDoc
671
- *
672
- * @param options
673
- * @param options.skipChecks Skip checks like making sure init signature is still valid and swap
674
- * wasn't commited yet (this is handled on swap creation, if you commit right after quoting, you
675
- * can use `skipChecks=true`)
676
- * @param options.secret A swap secret to use for the claim transaction, generally only needed if the swap
677
- * was recovered from on-chain data, or the pre-image was generated outside the SDK
678
- */
679
- async getCurrentActions(options?: {
680
- skipChecks?: boolean,
681
- secret?: string
682
- }): Promise<SwapExecutionAction<T>[]> {
683
- try {
684
- return await this.txsExecute(options);
685
- } catch (e) {
686
- return [];
687
- }
688
- }
689
-
690
-
691
- //////////////////////////////
692
- //// Payment
693
-
694
- /**
695
- * Checks whether the LP received the LN payment and we can continue by committing & claiming the HTLC on-chain
696
- *
697
- * @param save If the new swap state should be saved
698
- *
699
- * @internal
700
- */
701
- async _checkIntermediaryPaymentReceived(save: boolean = true): Promise<boolean | null> {
702
- if(
703
- this._state===FromBTCLNSwapState.PR_PAID ||
704
- this._state===FromBTCLNSwapState.CLAIM_COMMITED ||
705
- this._state===FromBTCLNSwapState.CLAIM_CLAIMED ||
706
- this._state===FromBTCLNSwapState.FAILED ||
707
- this._state===FromBTCLNSwapState.EXPIRED
708
- ) return true;
709
- if(this._state===FromBTCLNSwapState.QUOTE_EXPIRED || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData!=null)) return false;
710
- if(this.url==null) return false;
711
-
712
- const paymentHash = this.getPaymentHash();
713
- if(paymentHash==null)
714
- throw new Error("Failed to check LP payment received, payment hash not known (probably recovered swap?)");
715
-
716
- const resp = await IntermediaryAPI.getPaymentAuthorization(this.url, paymentHash.toString("hex"));
717
- switch(resp.code) {
718
- case PaymentAuthorizationResponseCodes.AUTH_DATA:
719
- const data = new (this.wrapper._swapDataDeserializer(this._contractVersion))(resp.data.data);
720
- try {
721
- await this.checkIntermediaryReturnedAuthData(this._getInitiator(), data, resp.data);
722
- this.expiry = await this._contract.getInitAuthorizationExpiry(
723
- data,
724
- resp.data
725
- );
726
- this._state = FromBTCLNSwapState.PR_PAID;
727
- this._data = data;
728
- this.signatureData = {
729
- prefix: resp.data.prefix,
730
- timeout: resp.data.timeout,
731
- signature: resp.data.signature
732
- };
733
- this.initiated = true;
734
- if(save) await this._saveAndEmit();
735
- return true;
736
- } catch (e) {}
737
- return null;
738
- case PaymentAuthorizationResponseCodes.EXPIRED:
739
- this._state = FromBTCLNSwapState.QUOTE_EXPIRED;
740
- this.initiated = true;
741
- if(save) await this._saveAndEmit();
742
- return false;
743
- default:
744
- return null;
745
- }
746
- }
747
-
748
- /**
749
- * Checks the data returned by the intermediary in the payment auth request
750
- *
751
- * @param signer Smart chain signer's address initiating the swap
752
- * @param data Parsed swap data as returned by the intermediary
753
- * @param signature Signature data as returned by the intermediary
754
- *
755
- * @throws {IntermediaryError} If the returned are not valid
756
- * @throws {SignatureVerificationError} If the returned signature is not valid
757
- * @throws {Error} If the swap is already committed on-chain
758
- *
759
- * @internal
760
- */
761
- protected async checkIntermediaryReturnedAuthData(signer: string, data: T["Data"], signature: SignatureData): Promise<void> {
762
- data.setClaimer(signer);
763
-
764
- if (data.getType() !== ChainSwapType.HTLC) throw new IntermediaryError("Invalid swap type");
765
- if (!data.isOfferer(this.getSwapData().getOfferer())) throw new IntermediaryError("Invalid offerer used");
766
- if (!data.isClaimer(this._getInitiator())) throw new IntermediaryError("Invalid claimer used");
767
- if (!data.isToken(this.getSwapData().getToken())) throw new IntermediaryError("Invalid token used");
768
- if (data.getSecurityDeposit() > this.getSwapData().getSecurityDeposit()) throw new IntermediaryError("Invalid security deposit!");
769
- if (data.getClaimerBounty() !== 0n) throw new IntermediaryError("Invalid claimer bounty!");
770
- if (data.getAmount() < this.getSwapData().getAmount()) throw new IntermediaryError("Invalid amount received!");
771
- if (data.getClaimHash() !== this.getSwapData().getClaimHash()) throw new IntermediaryError("Invalid payment hash used!");
772
- if (!data.isDepositToken(this.getSwapData().getDepositToken())) throw new IntermediaryError("Invalid deposit token used!");
773
- if (data.hasSuccessAction()) throw new IntermediaryError("Invalid has success action");
774
-
775
- await Promise.all([
776
- this._contract.isValidInitAuthorization(this._getInitiator(), data, signature, this.feeRate),
777
- this._contract.getCommitStatus(data.getClaimer(), data)
778
- .then(status => {
779
- if (status?.type !== SwapCommitStateType.NOT_COMMITED)
780
- throw new Error("Swap already committed on-chain!");
781
- })
782
- ]);
783
- }
784
-
785
- /**
786
- * Waits till a lightning network payment is received by the intermediary and client
787
- * can continue by initiating (committing) & settling (claiming) the HTLC by calling
788
- * either the {@link commitAndClaim} function (if the underlying chain allows commit
789
- * and claim in a single transaction - check with {@link canCommitAndClaimInOneShot}).
790
- * Or call {@link commit} and then {@link claim} separately.
791
- *
792
- * If this swap is using an LNURL-withdraw link as input, it automatically posts the
793
- * generated invoice to the LNURL service to pay it.
794
- *
795
- * @param onPaymentReceived Callback as for when the LP reports having received the ln payment
796
- * @param abortSignal Abort signal to stop waiting for payment
797
- * @param checkIntervalSeconds How often to poll the intermediary for answer (default 5 seconds)
798
- */
799
- async waitForPayment(onPaymentReceived?: (txId: string) => void, checkIntervalSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
800
- checkIntervalSeconds ??= 5;
801
- if(
802
- this._state!==FromBTCLNSwapState.PR_CREATED &&
803
- (this._state!==FromBTCLNSwapState.QUOTE_SOFT_EXPIRED || this.signatureData!=null)
804
- ) throw new Error("Must be in PR_CREATED state!");
805
- if(this.url==null) throw new Error("LP URL not known, cannot await the payment!");
806
-
807
- const abortController = new AbortController();
808
- if(abortSignal!=null) abortSignal.addEventListener("abort", () => abortController.abort(abortSignal.reason));
809
-
810
- let save = false;
811
-
812
- if(this.lnurl!=null && this.lnurlK1!=null && this.lnurlCallback!=null && !this.prPosted) {
813
- if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
814
- throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
815
-
816
- LNURL.postInvoiceToLNURLWithdraw({k1: this.lnurlK1, callback: this.lnurlCallback}, this.pr).catch(e => {
817
- this.lnurlFailSignal.abort(e);
818
- });
819
- this.prPosted = true;
820
- save ||= true;
821
- }
822
-
823
- if(!this.initiated) {
824
- this.initiated = true;
825
- save ||= true;
826
- }
827
-
828
- if(save) await this._saveAndEmit();
829
-
830
- let lnurlFailListener = () => abortController.abort(this.lnurlFailSignal.signal.reason);
831
- this.lnurlFailSignal.signal.addEventListener("abort", lnurlFailListener);
832
- this.lnurlFailSignal.signal.throwIfAborted();
833
-
834
- const paymentHash = this.getPaymentHash();
835
- if(paymentHash==null)
836
- throw new Error("Swap payment hash not available, the swap was probably recovered!");
837
-
838
- let resp: PaymentAuthorizationResponse = {code: PaymentAuthorizationResponseCodes.PENDING, msg: ""};
839
- while(!abortController.signal.aborted && resp.code===PaymentAuthorizationResponseCodes.PENDING) {
840
- resp = await IntermediaryAPI.getPaymentAuthorization(this.url, paymentHash.toString("hex"));
841
- if(resp.code===PaymentAuthorizationResponseCodes.PENDING)
842
- await timeoutPromise(checkIntervalSeconds*1000, abortController.signal);
843
- }
844
- this.lnurlFailSignal.signal.removeEventListener("abort", lnurlFailListener);
845
- abortController.signal.throwIfAborted();
846
-
847
- if(resp.code===PaymentAuthorizationResponseCodes.AUTH_DATA) {
848
- const sigData = resp.data;
849
- const swapData = new (this.wrapper._swapDataDeserializer(this._contractVersion))(resp.data.data);
850
- await this.checkIntermediaryReturnedAuthData(this._getInitiator(), swapData, sigData);
851
- this.expiry = await this._contract.getInitAuthorizationExpiry(
852
- swapData,
853
- sigData
854
- );
855
- if(onPaymentReceived!=null) onPaymentReceived(this.getInputTxId()!);
856
- if(this._state===FromBTCLNSwapState.PR_CREATED || this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED) {
857
- this._data = swapData;
858
- this.signatureData = {
859
- prefix: sigData.prefix,
860
- timeout: sigData.timeout,
861
- signature: sigData.signature
862
- };
863
- await this._saveAndEmit(FromBTCLNSwapState.PR_PAID);
864
- }
865
- return true;
866
- }
867
-
868
- if(this._state===FromBTCLNSwapState.PR_CREATED || this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED) {
869
- if(resp.code===PaymentAuthorizationResponseCodes.EXPIRED) {
870
- await this._saveAndEmit(FromBTCLNSwapState.QUOTE_EXPIRED);
871
- }
872
-
873
- return false;
874
- }
875
-
876
- throw new IntermediaryError("Invalid response from the LP");
877
- }
878
-
879
-
880
- //////////////////////////////
881
- //// Commit
882
-
883
- /**
884
- * @inheritDoc
885
- *
886
- * @throws {Error} If invalid signer is provided that doesn't match the swap data
887
- */
888
- async commit(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, skipChecks?: boolean, onBeforeTxSent?: (txId: string) => void): Promise<string> {
889
- const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
890
- this.checkSigner(signer);
891
- let txCount = 0;
892
- const txs = await this.txsCommit(skipChecks);
893
- const result = await this.wrapper._chain.sendAndConfirm(
894
- signer, txs, true, abortSignal, undefined, (txId: string) => {
895
- txCount++;
896
- if(onBeforeTxSent!=null && txCount===txs.length) onBeforeTxSent(txId);
897
- return Promise.resolve();
898
- }
899
- );
900
-
901
- this._commitTxId = result[result.length-1];
902
- if(this._state===FromBTCLNSwapState.PR_PAID || this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED || this._state===FromBTCLNSwapState.QUOTE_EXPIRED) {
903
- await this._saveAndEmit(FromBTCLNSwapState.CLAIM_COMMITED);
904
- }
905
- return this._commitTxId;
906
- }
907
-
908
- /**
909
- * @inheritDoc
910
- */
911
- async waitTillCommited(abortSignal?: AbortSignal): Promise<void> {
912
- if(this._state===FromBTCLNSwapState.CLAIM_COMMITED || this._state===FromBTCLNSwapState.CLAIM_CLAIMED) return Promise.resolve();
913
- if(this._state!==FromBTCLNSwapState.PR_PAID && (this._state!==FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData!=null)) throw new Error("Invalid state");
914
-
915
- const abortController = extendAbortController(abortSignal);
916
- const result = await Promise.race([
917
- this.watchdogWaitTillCommited(undefined, abortController.signal),
918
- this.waitTillState(FromBTCLNSwapState.CLAIM_COMMITED, "gte", abortController.signal).then(() => 0)
919
- ]);
920
- abortController.abort();
921
-
922
- if(result===0) this.logger.debug("waitTillCommited(): Resolved from state changed");
923
- if(result===true) this.logger.debug("waitTillCommited(): Resolved from watchdog - commited");
924
- if(result===false) {
925
- this.logger.debug("waitTillCommited(): Resolved from watchdog - signature expired");
926
- if(
927
- this._state===FromBTCLNSwapState.PR_PAID ||
928
- this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED
929
- ) {
930
- await this._saveAndEmit(FromBTCLNSwapState.QUOTE_EXPIRED);
931
- }
932
- return;
933
- }
934
-
935
- if(
936
- this._state===FromBTCLNSwapState.PR_PAID ||
937
- this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED
938
- ) {
939
- await this._saveAndEmit(FromBTCLNSwapState.CLAIM_COMMITED);
940
- }
941
- }
942
-
943
-
944
- //////////////////////////////
945
- //// Claim
946
-
947
- /**
948
- * Unsafe txs claim getter without state checking!
949
- *
950
- * @param _signer
951
- * @param secret A swap secret to use for the claim transaction, generally only needed if the swap
952
- * was recovered from on-chain data, or the pre-image was generated outside the SDK
953
- *
954
- * @internal
955
- */
956
- private async _txsClaim(_signer?: string | T["Signer"] | T["NativeSigner"], secret?: string): Promise<T["TX"][]> {
957
- let address: string | undefined = undefined;
958
- if(_signer!=null) {
959
- if (typeof (_signer) === "string") {
960
- address = _signer;
961
- } else if (isAbstractSigner(_signer)) {
962
- address = _signer.getAddress();
963
- } else {
964
- address = (await this.wrapper._chain.wrapSigner(_signer)).getAddress();
965
- }
966
- }
967
-
968
- if(this._data==null) throw new Error("Unknown data, wrong state?");
969
- const useSecret = secret ?? this.secret;
970
- if(useSecret==null)
971
- throw new Error("Swap secret pre-image not known and not provided, please provide the swap secret pre-image as an argument");
972
- if(!this.isValidSecretPreimage(useSecret))
973
- throw new Error("Invalid swap secret pre-image provided!");
974
-
975
- return this._contract.txsClaimWithSecret(
976
- address ?? this._getInitiator(),
977
- this._data, useSecret, true, true
978
- );
979
- }
980
-
981
- /**
982
- * @inheritDoc
983
- *
984
- * @param _signer Optional signer address to use for claiming the swap, can also be different from the initializer
985
- * @param secret A swap secret to use for the claim transaction, generally only needed if the swap
986
- * was recovered from on-chain data, or the pre-image was generated outside the SDK
987
- *
988
- * @throws {Error} If in invalid state (must be {@link FromBTCLNSwapState.CLAIM_COMMITED})
989
- */
990
- async txsClaim(_signer?: string | T["Signer"] | T["NativeSigner"], secret?: string): Promise<T["TX"][]> {
991
- if(this._state!==FromBTCLNSwapState.CLAIM_COMMITED) throw new Error("Must be in CLAIM_COMMITED state!");
992
- return this._txsClaim(_signer, secret);
993
- }
994
-
995
- /**
996
- * @inheritDoc
997
- *
998
- * @param _signer
999
- * @param abortSignal
1000
- * @param onBeforeTxSent
1001
- * @param secret A swap secret to use for the claim transaction, generally only needed if the swap
1002
- * was recovered from on-chain data, or the pre-image was generated outside the SDK
1003
- */
1004
- async claim(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, onBeforeTxSent?: (txId: string) => void, secret?: string): Promise<string> {
1005
- const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
1006
- let txCount = 0;
1007
- const result = await this.wrapper._chain.sendAndConfirm(
1008
- signer, await this.txsClaim(_signer, secret), true, abortSignal, undefined, (txId: string) => {
1009
- txCount++;
1010
- if(onBeforeTxSent!=null && txCount===1) onBeforeTxSent(txId);
1011
- return Promise.resolve();
1012
- }
1013
- );
1014
-
1015
- this._claimTxId = result[0];
1016
- if(this._state===FromBTCLNSwapState.CLAIM_COMMITED || this._state===FromBTCLNSwapState.EXPIRED || this._state===FromBTCLNSwapState.FAILED) {
1017
- await this._saveAndEmit(FromBTCLNSwapState.CLAIM_CLAIMED);
1018
- }
1019
- return result[0];
1020
- }
1021
-
1022
- /**
1023
- * @inheritDoc
1024
- *
1025
- * @throws {Error} If swap is in invalid state (must be {@link FromBTCLNSwapState.CLAIM_COMMITED})
1026
- * @throws {Error} If the LP refunded sooner than we were able to claim
1027
- */
1028
- async waitTillClaimed(maxWaitTimeSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
1029
- if(this._state===FromBTCLNSwapState.CLAIM_CLAIMED) return Promise.resolve(true);
1030
- if(this._state!==FromBTCLNSwapState.CLAIM_COMMITED) throw new Error("Invalid state (not CLAIM_COMMITED)");
1031
-
1032
- const abortController = new AbortController();
1033
- if(abortSignal!=null) abortSignal.addEventListener("abort", () => abortController.abort(abortSignal.reason));
1034
- let timedOut: boolean = false;
1035
- if(maxWaitTimeSeconds!=null) {
1036
- const timeout = setTimeout(() => {
1037
- timedOut = true;
1038
- abortController.abort();
1039
- }, maxWaitTimeSeconds * 1000);
1040
- abortController.signal.addEventListener("abort", () => clearTimeout(timeout));
1041
- }
1042
-
1043
- let res: 0 | 1 | SwapCommitState;
1044
- try {
1045
- res = await Promise.race([
1046
- this.watchdogWaitTillResult(undefined, abortController.signal),
1047
- this.waitTillState(FromBTCLNSwapState.CLAIM_CLAIMED, "eq", abortController.signal).then(() => 0 as const),
1048
- this.waitTillState(FromBTCLNSwapState.EXPIRED, "eq", abortController.signal).then(() => 1 as const),
1049
- ]);
1050
- abortController.abort();
1051
- } catch (e) {
1052
- abortController.abort();
1053
- if(timedOut) return false;
1054
- throw e;
1055
- }
1056
-
1057
- if(res===0) {
1058
- this.logger.debug("waitTillClaimed(): Resolved from state change (CLAIM_CLAIMED)");
1059
- return true;
1060
- }
1061
- if(res===1) {
1062
- this.logger.debug("waitTillClaimed(): Resolved from state change (EXPIRED)");
1063
- throw new Error("Swap expired during claiming");
1064
- }
1065
- this.logger.debug("waitTillClaimed(): Resolved from watchdog");
1066
-
1067
- if(res?.type===SwapCommitStateType.PAID) {
1068
- if((this._state as FromBTCLNSwapState)!==FromBTCLNSwapState.CLAIM_CLAIMED) {
1069
- this._claimTxId = await res.getClaimTxId();
1070
- await this._saveAndEmit(FromBTCLNSwapState.CLAIM_CLAIMED);
1071
- }
1072
- }
1073
- if(res?.type===SwapCommitStateType.NOT_COMMITED || res?.type===SwapCommitStateType.EXPIRED) {
1074
- if(
1075
- (this._state as FromBTCLNSwapState)!==FromBTCLNSwapState.CLAIM_CLAIMED &&
1076
- (this._state as FromBTCLNSwapState)!==FromBTCLNSwapState.FAILED
1077
- ) {
1078
- if(res.getRefundTxId!=null) this._refundTxId = await res.getRefundTxId();
1079
- await this._saveAndEmit(FromBTCLNSwapState.FAILED);
1080
- }
1081
- throw new Error("Swap expired while waiting for claim!");
1082
- }
1083
- return true;
1084
- }
1085
-
1086
-
1087
- //////////////////////////////
1088
- //// Commit & claim
1089
-
1090
- /**
1091
- * Estimated transaction fee for commit & claim transactions combined, required
1092
- * to settle the swap on the smart chain destination side.
1093
- */
1094
- async getCommitAndClaimNetworkFee(): Promise<TokenAmount<SCToken<T["ChainId"]>, true>> {
1095
- const swapContract: T["Contract"] = this._contract;
1096
- const feeRate = this.feeRate ?? await swapContract.getInitFeeRate(
1097
- this.getSwapData().getOfferer(),
1098
- this.getSwapData().getClaimer(),
1099
- this.getSwapData().getToken(),
1100
- this.getSwapData().getClaimHash()
1101
- );
1102
- const commitFee = await (
1103
- swapContract.getRawCommitFee!=null ?
1104
- swapContract.getRawCommitFee(this._getInitiator(), this.getSwapData(), feeRate) :
1105
- swapContract.getCommitFee(this._getInitiator(), this.getSwapData(), feeRate)
1106
- );
1107
- const claimFee = await (
1108
- swapContract.getRawClaimFee!=null ?
1109
- swapContract.getRawClaimFee(this._getInitiator(), this.getSwapData(), feeRate) :
1110
- swapContract.getClaimFee(this._getInitiator(), this.getSwapData(), feeRate)
1111
- );
1112
-
1113
- return toTokenAmount(
1114
- commitFee + claimFee,
1115
- this.wrapper._getNativeToken(),
1116
- this.wrapper._prices
1117
- );
1118
- }
1119
-
1120
- /**
1121
- * Returns whether the underlying chain supports calling commit and claim in a single call,
1122
- * such that you can use the {@link commitAndClaim} function. If not you have to manually
1123
- * call {@link commit} first and then {@link claim}.
1124
- */
1125
- canCommitAndClaimInOneShot(): boolean {
1126
- return this._contract.initAndClaimWithSecret!=null;
1127
- }
1128
-
1129
- /**
1130
- * Returns transactions for both commit & claim operation together, such that they can be signed all at once by
1131
- * the wallet. **WARNING**: transactions must be sent sequentially, such that the claim (2nd) transaction is only
1132
- * sent after the commit (1st) transaction confirms. Failure to do so can reveal the HTLC pre-image too soon,
1133
- * opening a possibility for the LP to steal funds!
1134
- *
1135
- * @param skipChecks Skip checks like making sure init signature is still valid and swap wasn't commited yet
1136
- * (this is handled when swap is created (quoted), if you commit right after quoting, you can use skipChecks=true)
1137
- * @param secret A swap secret to use for the claim transaction, generally only needed if the swap
1138
- * was recovered from on-chain data, or the pre-image was generated outside the SDK
1139
- *
1140
- * @throws {Error} If in invalid state (must be PR_PAID or CLAIM_COMMITED)
1141
- */
1142
- async txsCommitAndClaim(skipChecks?: boolean, secret?: string): Promise<T["TX"][]> {
1143
- if(this._state===FromBTCLNSwapState.CLAIM_COMMITED) return await this.txsClaim(undefined, secret);
1144
- if(
1145
- this._state!==FromBTCLNSwapState.PR_PAID &&
1146
- (this._state!==FromBTCLNSwapState.QUOTE_SOFT_EXPIRED || this.signatureData==null)
1147
- ) throw new Error("Must be in PR_PAID state!");
1148
- if(this._data==null) throw new Error("Unknown data, wrong state?");
1149
-
1150
- const useSecret = secret ?? this.secret;
1151
- if(useSecret==null)
1152
- throw new Error("Swap secret pre-image not known and not provided, please provide the swap secret pre-image as second argument");
1153
- if(!this.isValidSecretPreimage(useSecret))
1154
- throw new Error("Invalid swap secret pre-image provided!");
1155
-
1156
- const initTxs = await this.txsCommit(skipChecks);
1157
- const claimTxs = await this._contract.txsClaimWithSecret(
1158
- this._getInitiator(), this._data, useSecret,
1159
- true, true, undefined,
1160
- true
1161
- );
1162
-
1163
- return initTxs.concat(claimTxs);
1164
- }
1165
-
1166
- /**
1167
- * Commits and claims the swap, in a way that the transactions can be signed together by the provided signer and
1168
- * then automatically sent sequentially by the SDK. To check if the underlying chain supports this flow check
1169
- * the {@link canCommitAndClaimInOneShot} function.
1170
- *
1171
- * @param _signer Signer to sign the transactions with, must be the same as used in the initialization
1172
- * @param abortSignal Abort signal to stop waiting for the transaction confirmation and abort
1173
- * @param skipChecks Skip checks like making sure init signature is still valid and swap wasn't commited yet
1174
- * (this is handled when swap is created (quoted), if you commit right after quoting, you can use skipChecks=true)
1175
- * @param onBeforeCommitTxSent Optional callback called before the initialization (commit) transaction is
1176
- * broadcasted
1177
- * @param onBeforeClaimTxSent Optional callback called before the settlement (claim) transaction is
1178
- * broadcasted
1179
- * @param secret A swap secret to use for the claim transaction, generally only needed if the swap
1180
- * was recovered from on-chain data, or the pre-image was generated outside the SDK
1181
- *
1182
- * @throws {Error} If in invalid state (must be PR_PAID or CLAIM_COMMITED)
1183
- * @throws {Error} If invalid signer is provided that doesn't match the swap data
1184
- */
1185
- async commitAndClaim(
1186
- _signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, skipChecks?: boolean,
1187
- onBeforeCommitTxSent?: (txId: string) => void, onBeforeClaimTxSent?: (txId: string) => void,
1188
- secret?: string
1189
- ): Promise<string[]> {
1190
- const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
1191
- if(!this.canCommitAndClaimInOneShot()) throw new Error("Cannot commitAndClaim in single action, please run commit and claim separately!");
1192
- this.checkSigner(signer);
1193
- if(this._state===FromBTCLNSwapState.CLAIM_COMMITED) return [await this.claim(signer, abortSignal, onBeforeClaimTxSent, secret)];
1194
-
1195
- let txCount = 0;
1196
- const txs = await this.txsCommitAndClaim(skipChecks, secret);
1197
- const result = await this.wrapper._chain.sendAndConfirm(
1198
- signer, txs, true, abortSignal, undefined, (txId: string) => {
1199
- txCount++;
1200
- if(onBeforeCommitTxSent!=null && txCount===1) onBeforeCommitTxSent(txId);
1201
- if(onBeforeClaimTxSent!=null && txCount===txs.length) onBeforeClaimTxSent(txId);
1202
- return Promise.resolve();
1203
- }
1204
- );
1205
-
1206
- this._commitTxId = result[0] ?? this._commitTxId;
1207
- this._claimTxId = result[result.length-1] ?? this._claimTxId;
1208
- if(this._state!==FromBTCLNSwapState.CLAIM_CLAIMED) {
1209
- await this._saveAndEmit(FromBTCLNSwapState.CLAIM_CLAIMED);
1210
- }
1211
-
1212
- return result;
1213
- }
1214
-
1215
-
1216
- //////////////////////////////
1217
- //// LNURL
1218
-
1219
- /**
1220
- * Whether this swap uses an LNURL-withdraw link
1221
- */
1222
- isLNURL(): boolean {
1223
- return this.lnurl!=null;
1224
- }
1225
-
1226
- /**
1227
- * Gets the used LNURL or `null` if this is not an LNURL-withdraw swap
1228
- */
1229
- getLNURL(): string | null {
1230
- return this.lnurl ?? null;
1231
- }
1232
-
1233
- /**
1234
- * Pay the generated lightning network invoice with an LNURL-withdraw link, this
1235
- * is useful when you want to display a lightning payment QR code and also want to
1236
- * allow payments using LNURL-withdraw NFC cards.
1237
- *
1238
- * Note that the swap needs to be created **without** an LNURL to begin with for this function
1239
- * to work. If this swap is already using an LNURL-withdraw link, this function throws.
1240
- */
1241
- async settleWithLNURLWithdraw(lnurl: string | LNURLWithdraw): Promise<void> {
1242
- if(
1243
- this._state!==FromBTCLNSwapState.PR_CREATED &&
1244
- (this._state!==FromBTCLNSwapState.QUOTE_SOFT_EXPIRED || this.signatureData!=null)
1245
- ) throw new Error("Must be in PR_CREATED state!");
1246
-
1247
- if(this.lnurl!=null) throw new Error("Cannot settle LNURL-withdraw swap with different LNURL");
1248
- let lnurlParams: LNURLWithdrawParamsWithUrl;
1249
- if(typeof(lnurl)==="string") {
1250
- const parsedLNURL = await LNURL.getLNURL(lnurl);
1251
- if(parsedLNURL==null || parsedLNURL.tag!=="withdrawRequest")
1252
- throw new UserError("Invalid LNURL-withdraw to settle the swap");
1253
- lnurlParams = parsedLNURL;
1254
- } else {
1255
- lnurlParams = lnurl.params;
1256
- }
1257
-
1258
- if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
1259
- throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
1260
-
1261
- LNURL.useLNURLWithdraw(lnurlParams, this.pr).catch(e => this.lnurlFailSignal.abort(e));
1262
- this.lnurl = lnurlParams.url;
1263
- this.lnurlCallback = lnurlParams.callback;
1264
- this.lnurlK1 = lnurlParams.k1;
1265
- this.prPosted = true;
1266
- await this._saveAndEmit();
1267
- }
1268
-
1269
-
1270
- //////////////////////////////
1271
- //// Storage
1272
-
1273
- /**
1274
- * @inheritDoc
1275
- */
1276
- serialize(): any {
1277
- return {
1278
- ...super.serialize(),
1279
- pr: this.pr,
1280
- secret: this.secret,
1281
- lnurl: this.lnurl,
1282
- lnurlK1: this.lnurlK1,
1283
- lnurlCallback: this.lnurlCallback,
1284
- prPosted: this.prPosted,
1285
- initialSwapData: this.initialSwapData.serialize(),
1286
- usesClaimHashAsId: this.usesClaimHashAsId
1287
- };
1288
- }
1289
-
1290
-
1291
- //////////////////////////////
1292
- //// Swap ticks & sync
1293
-
1294
- /**
1295
- * Checks the swap's state on-chain and compares it to its internal state, updates/changes it according to on-chain
1296
- * data
1297
- *
1298
- * @private
1299
- */
1300
- private async syncStateFromChain(quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState): Promise<boolean> {
1301
- if(
1302
- this._state===FromBTCLNSwapState.PR_PAID ||
1303
- (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData!=null) ||
1304
- this._state===FromBTCLNSwapState.CLAIM_COMMITED ||
1305
- this._state===FromBTCLNSwapState.EXPIRED
1306
- ) {
1307
- //Check for expiry before the getCommitStatus to prevent race conditions
1308
- let quoteExpired: boolean = false;
1309
- if(this._state===FromBTCLNSwapState.PR_PAID || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData!=null)) {
1310
- quoteExpired = quoteDefinitelyExpired ?? await this._verifyQuoteDefinitelyExpired();
1311
- }
1312
-
1313
- //Check if it's already successfully paid
1314
- commitStatus ??= await this._contract.getCommitStatus(this._getInitiator(), this._data!);
1315
- if(commitStatus!=null && await this._forciblySetOnchainState(commitStatus)) return true;
1316
-
1317
- //Set the state on expiry here
1318
- if(this._state===FromBTCLNSwapState.PR_PAID || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData!=null)) {
1319
- if(quoteExpired) {
1320
- this._state = FromBTCLNSwapState.QUOTE_EXPIRED;
1321
- return true;
1322
- }
1323
- }
1324
- }
1325
- return false;
1326
- }
1327
-
1328
- /**
1329
- * @inheritDoc
1330
- * @internal
1331
- */
1332
- _shouldFetchExpiryStatus(): boolean {
1333
- return this._state===FromBTCLNSwapState.PR_PAID || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData!=null);
1334
- }
1335
-
1336
- /**
1337
- * @inheritDoc
1338
- * @internal
1339
- */
1340
- _shouldFetchOnchainState(): boolean {
1341
- return this._state===FromBTCLNSwapState.PR_PAID || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData!=null) ||
1342
- this._state===FromBTCLNSwapState.CLAIM_COMMITED || this._state===FromBTCLNSwapState.EXPIRED;
1343
- }
1344
-
1345
- /**
1346
- * Whether an intermediary (LP) should be contacted to get the state of this swap.
1347
- *
1348
- * @internal
1349
- */
1350
- _shouldCheckIntermediary(): boolean {
1351
- return this._state===FromBTCLNSwapState.PR_CREATED || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData==null);
1352
- }
1353
-
1354
- /**
1355
- * @inheritDoc
1356
- * @internal
1357
- */
1358
- async _sync(save?: boolean, quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState, skipLpCheck?: boolean): Promise<boolean> {
1359
- let changed = false;
1360
-
1361
- if(this._state===FromBTCLNSwapState.PR_CREATED || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData==null)) {
1362
- if(this._state!=FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.getTimeoutTime()<Date.now()) {
1363
- this._state = FromBTCLNSwapState.QUOTE_SOFT_EXPIRED;
1364
- changed ||= true;
1365
- }
1366
-
1367
- if(!skipLpCheck) try {
1368
- const result = await this._checkIntermediaryPaymentReceived(false);
1369
- if(result!==null) changed ||= true;
1370
- } catch (e) {
1371
- this.logger.error("_sync(): Failed to synchronize swap, error: ", e);
1372
- }
1373
-
1374
- if(this._state===FromBTCLNSwapState.PR_CREATED || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData==null)) {
1375
- if(await this._verifyQuoteDefinitelyExpired()) {
1376
- this._state = FromBTCLNSwapState.QUOTE_EXPIRED;
1377
- changed ||= true;
1378
- }
1379
- }
1380
- }
1381
-
1382
- if(await this.syncStateFromChain(quoteDefinitelyExpired, commitStatus)) changed = true;
1383
-
1384
- if(this._state===FromBTCLNSwapState.CLAIM_COMMITED) {
1385
- const expired = await this._contract.isExpired(this._getInitiator(), this._data!);
1386
- if(expired) {
1387
- this._state = FromBTCLNSwapState.EXPIRED;
1388
- changed = true;
1389
- }
1390
- }
1391
-
1392
- if(save && changed) await this._saveAndEmit();
1393
-
1394
- return changed;
1395
- }
1396
-
1397
- /**
1398
- * @inheritDoc
1399
- * @internal
1400
- */
1401
- async _forciblySetOnchainState(commitStatus: SwapCommitState): Promise<boolean> {
1402
- switch(commitStatus.type) {
1403
- case SwapCommitStateType.PAID:
1404
- if(this._claimTxId==null) this._claimTxId = await commitStatus.getClaimTxId();
1405
- if(this.secret==null || this.pr==null) this._setSwapSecret(await commitStatus.getClaimResult());
1406
- this._state = FromBTCLNSwapState.CLAIM_CLAIMED;
1407
- return true;
1408
- case SwapCommitStateType.NOT_COMMITED:
1409
- if(this._refundTxId==null && commitStatus.getRefundTxId) this._refundTxId = await commitStatus.getRefundTxId();
1410
- if(this._refundTxId!=null) {
1411
- this._state = FromBTCLNSwapState.FAILED;
1412
- return true;
1413
- }
1414
- break;
1415
- case SwapCommitStateType.EXPIRED:
1416
- if(this._refundTxId==null && commitStatus.getRefundTxId) this._refundTxId = await commitStatus.getRefundTxId();
1417
- this._state = this._refundTxId==null ? FromBTCLNSwapState.QUOTE_EXPIRED : FromBTCLNSwapState.FAILED;
1418
- return true;
1419
- case SwapCommitStateType.COMMITED:
1420
- if(this._state!==FromBTCLNSwapState.CLAIM_COMMITED && this._state!==FromBTCLNSwapState.EXPIRED) {
1421
- this._state = FromBTCLNSwapState.CLAIM_COMMITED;
1422
- return true;
1423
- }
1424
- break;
1425
- }
1426
- return false;
1427
- }
1428
-
1429
- /**
1430
- * @inheritDoc
1431
- * @internal
1432
- */
1433
- async _tick(save?: boolean): Promise<boolean> {
1434
- switch(this._state) {
1435
- case FromBTCLNSwapState.PR_CREATED:
1436
- if(this.getTimeoutTime()<Date.now()) {
1437
- this._state = FromBTCLNSwapState.QUOTE_SOFT_EXPIRED;
1438
- if(save) await this._saveAndEmit();
1439
- return true;
1440
- }
1441
- break;
1442
- case FromBTCLNSwapState.PR_PAID:
1443
- if(this.expiry<Date.now()) {
1444
- this._state = FromBTCLNSwapState.QUOTE_SOFT_EXPIRED;
1445
- if(save) await this._saveAndEmit();
1446
- return true;
1447
- }
1448
- break;
1449
- case FromBTCLNSwapState.CLAIM_COMMITED:
1450
- const expired = await this._contract.isExpired(this._getInitiator(), this._data!);
1451
- if(expired) {
1452
- this._state = FromBTCLNSwapState.EXPIRED;
1453
- if(save) await this._saveAndEmit();
1454
- return true;
1455
- }
1456
- break;
1457
- }
1458
-
1459
- return false;
1460
- }
1461
-
1462
- /**
1463
- * Forcibly sets the swap secret pre-image from on-chain data
1464
- *
1465
- * @internal
1466
- */
1467
- _setSwapSecret(secret: string) {
1468
- this.secret = secret;
1469
- if(this.pr==null) {
1470
- this.pr = Buffer.from(sha256(Buffer.from(secret, "hex"))).toString("hex");
1471
- }
1472
- }
1473
-
1474
- }
1
+ import {decode as bolt11Decode} from "@atomiqlabs/bolt11";
2
+ import {FromBTCLNDefinition, FromBTCLNWrapper} from "./FromBTCLNWrapper";
3
+ import {IFromBTCSelfInitSwap} from "../IFromBTCSelfInitSwap";
4
+ import {SwapType} from "../../../../enums/SwapType";
5
+ import {
6
+ ChainSwapType,
7
+ ChainType, isAbstractSigner,
8
+ SignatureData,
9
+ SwapCommitState,
10
+ SwapCommitStateType,
11
+ SwapData,
12
+ SignatureVerificationError
13
+ } from "@atomiqlabs/base";
14
+ import {Buffer} from "buffer";
15
+ import {LNURL} from "../../../../lnurl/LNURL";
16
+ import {UserError} from "../../../../errors/UserError";
17
+ import {
18
+ IntermediaryAPI,
19
+ PaymentAuthorizationResponse,
20
+ PaymentAuthorizationResponseCodes
21
+ } from "../../../../intermediaries/apis/IntermediaryAPI";
22
+ import {IntermediaryError} from "../../../../errors/IntermediaryError";
23
+ import {extendAbortController} from "../../../../utils/Utils";
24
+ import {MinimalLightningNetworkWalletInterface} from "../../../../types/wallets/MinimalLightningNetworkWalletInterface";
25
+ import {IClaimableSwap} from "../../../IClaimableSwap";
26
+ import {IAddressSwap} from "../../../IAddressSwap";
27
+ import {IEscrowSelfInitSwapInit, isIEscrowSelfInitSwapInit} from "../../IEscrowSelfInitSwap";
28
+ import {TokenAmount, toTokenAmount} from "../../../../types/TokenAmount";
29
+ import {BitcoinTokens, BtcToken, SCToken} from "../../../../types/Token";
30
+ import {getLogger, LoggerType} from "../../../../utils/Logger";
31
+ import {timeoutPromise} from "../../../../utils/TimeoutUtils";
32
+ import {isLNURLWithdraw, LNURLWithdraw, LNURLWithdrawParamsWithUrl} from "../../../../types/lnurl/LNURLWithdraw";
33
+ import {sha256} from "@noble/hashes/sha2";
34
+ import {SwapExecutionAction} from "../../../../types/SwapExecutionAction";
35
+
36
+ /**
37
+ * State enum for legacy Lightning -> Smart chain swaps
38
+ * @category Swaps/Legacy/Lightning → Smart chain
39
+ */
40
+ export enum FromBTCLNSwapState {
41
+ /**
42
+ * Swap has failed as the user didn't settle the HTLC on the destination before expiration
43
+ */
44
+ FAILED = -4,
45
+ /**
46
+ * Swap has expired for good and there is no way how it can be executed anymore
47
+ */
48
+ QUOTE_EXPIRED = -3,
49
+ /**
50
+ * A swap is almost expired, and it should be presented to the user as expired, though
51
+ * there is still a chance that it will be processed
52
+ */
53
+ QUOTE_SOFT_EXPIRED = -2,
54
+ /**
55
+ * Swap HTLC on the destination chain has expired, it is not safe anymore to settle (claim) the
56
+ * swap on the destination smart chain.
57
+ */
58
+ EXPIRED = -1,
59
+ /**
60
+ * Swap quote was created, use {@link FromBTCLNSwap.getAddress} or {@link FromBTCLNSwap.getHyperlink}
61
+ * to get the bolt11 lightning network invoice to pay to initiate the swap, then use the
62
+ * {@link FromBTCLNSwap.waitForPayment} to wait till the lightning network payment is received
63
+ * by the intermediary (LP)
64
+ */
65
+ PR_CREATED = 0,
66
+ /**
67
+ * Lightning network payment has been received by the intermediary (LP), the user can now settle
68
+ * the swap on the destination smart chain side with {@link FromBTCLNSwap.commitAndClaim} (if
69
+ * the underlying chain supports it - check with {@link FromBTCLNSwap.canCommitAndClaimInOneShot}),
70
+ * or by calling {@link FromBTCLNSwap.commit} and {@link FromBTCLNSwap.claim} separately.
71
+ */
72
+ PR_PAID = 1,
73
+ /**
74
+ * Swap escrow HTLC has been created on the destination chain. Continue by claiming it with the
75
+ * {@link FromBTCLNSwap.claim} or {@link FromBTCLNSwap.txsClaim} function.
76
+ */
77
+ CLAIM_COMMITED = 2,
78
+ /**
79
+ * Swap successfully settled and funds received on the destination chain
80
+ */
81
+ CLAIM_CLAIMED = 3
82
+ }
83
+
84
+ const FromBTCLNSwapStateDescription = {
85
+ [FromBTCLNSwapState.FAILED]: "Swap has failed as the user didn't settle the HTLC on the destination before expiration",
86
+ [FromBTCLNSwapState.QUOTE_EXPIRED]: "Swap has expired for good and there is no way how it can be executed anymore",
87
+ [FromBTCLNSwapState.QUOTE_SOFT_EXPIRED]: "Swap is expired, though there is still a chance that it will be processed",
88
+ [FromBTCLNSwapState.EXPIRED]: "Swap HTLC on the destination chain has expired, it is not safe anymore to settle (claim) the swap on the destination smart chain.",
89
+ [FromBTCLNSwapState.PR_CREATED]: "Swap quote was created, pay the bolt11 lightning network invoice to initiate the swap, then use the wait till the lightning network payment is received by the intermediary (LP)",
90
+ [FromBTCLNSwapState.PR_PAID]: "Lightning network payment has been received by the intermediary (LP), the user can now settle the swap on the destination smart chain side.",
91
+ [FromBTCLNSwapState.CLAIM_COMMITED]: "Swap escrow HTLC has been created on the destination chain. Continue by claiming it.",
92
+ [FromBTCLNSwapState.CLAIM_CLAIMED]: "Swap successfully settled and funds received on the destination chain"
93
+ };
94
+
95
+ export type FromBTCLNSwapInit<T extends SwapData> = IEscrowSelfInitSwapInit<T> & {
96
+ pr?: string,
97
+ secret?: string,
98
+ initialSwapData: T,
99
+ lnurl?: string,
100
+ lnurlK1?: string,
101
+ lnurlCallback?: string
102
+ };
103
+
104
+ export function isFromBTCLNSwapInit<T extends SwapData>(obj: any): obj is FromBTCLNSwapInit<T> {
105
+ return (obj.pr==null || typeof obj.pr==="string") &&
106
+ (obj.secret==null || typeof obj.secret==="string") &&
107
+ (obj.lnurl==null || typeof(obj.lnurl)==="string") &&
108
+ (obj.lnurlK1==null || typeof(obj.lnurlK1)==="string") &&
109
+ (obj.lnurlCallback==null || typeof(obj.lnurlCallback)==="string") &&
110
+ isIEscrowSelfInitSwapInit(obj);
111
+ }
112
+
113
+ /**
114
+ * Legacy escrow (HTLC) based swap for Bitcoin Lightning -> Smart chains, requires manual settlement
115
+ * of the swap on the destination network once the lightning network payment is received by the LP.
116
+ *
117
+ * @category Swaps/Legacy/Lightning → Smart chain
118
+ */
119
+ export class FromBTCLNSwap<T extends ChainType = ChainType>
120
+ extends IFromBTCSelfInitSwap<T, FromBTCLNDefinition<T>, FromBTCLNSwapState>
121
+ implements IAddressSwap, IClaimableSwap<T, FromBTCLNDefinition<T>, FromBTCLNSwapState> {
122
+
123
+ protected readonly TYPE = SwapType.FROM_BTCLN;
124
+ /**
125
+ * @internal
126
+ */
127
+ protected readonly swapStateName = (state: number) => FromBTCLNSwapState[state];
128
+ /**
129
+ * @internal
130
+ */
131
+ protected readonly swapStateDescription = FromBTCLNSwapStateDescription;
132
+ /**
133
+ * @internal
134
+ */
135
+ protected readonly logger: LoggerType;
136
+ /**
137
+ * @internal
138
+ */
139
+ protected readonly inputToken: BtcToken<true> = BitcoinTokens.BTCLN;
140
+
141
+ private readonly lnurlFailSignal: AbortController = new AbortController();
142
+ private readonly usesClaimHashAsId: boolean;
143
+ private readonly initialSwapData: T["Data"];
144
+
145
+ /**
146
+ * In case the swap is recovered from on-chain data, the pr saved here is just a payment hash,
147
+ * as it is impossible to retrieve the actual lightning network invoice paid purely from on-chain
148
+ * data
149
+ * @private
150
+ */
151
+ private pr?: string;
152
+ private secret?: string;
153
+
154
+ private lnurl?: string;
155
+ private lnurlK1?: string;
156
+ private lnurlCallback?: string;
157
+ private prPosted?: boolean = false;
158
+
159
+ /**
160
+ * Sets the LNURL data for the swap
161
+ *
162
+ * @internal
163
+ */
164
+ _setLNURLData(lnurl: string, lnurlK1: string, lnurlCallback: string) {
165
+ this.lnurl = lnurl;
166
+ this.lnurlK1 = lnurlK1;
167
+ this.lnurlCallback = lnurlCallback;
168
+ }
169
+
170
+ constructor(wrapper: FromBTCLNWrapper<T>, init: FromBTCLNSwapInit<T["Data"]>);
171
+ constructor(wrapper: FromBTCLNWrapper<T>, obj: any);
172
+ constructor(
173
+ wrapper: FromBTCLNWrapper<T>,
174
+ initOrObject: FromBTCLNSwapInit<T["Data"]> | any
175
+ ) {
176
+ if(isFromBTCLNSwapInit(initOrObject) && initOrObject.url!=null) initOrObject.url += "/frombtcln";
177
+ super(wrapper, initOrObject);
178
+ if(isFromBTCLNSwapInit(initOrObject)) {
179
+ this._state = FromBTCLNSwapState.PR_CREATED;
180
+ this.pr = initOrObject.pr;
181
+ this.secret = initOrObject.secret;
182
+ this.initialSwapData = initOrObject.initialSwapData;
183
+ this.lnurl = initOrObject.lnurl;
184
+ this.lnurlK1 = initOrObject.lnurlK1;
185
+ this.lnurlCallback = initOrObject.lnurlCallback;
186
+ this.usesClaimHashAsId = true;
187
+ } else {
188
+ this.pr = initOrObject.pr;
189
+ this.secret = initOrObject.secret;
190
+
191
+ if(initOrObject.initialSwapData==null) {
192
+ this.initialSwapData = this._data!;
193
+ } else {
194
+ this.initialSwapData = SwapData.deserialize<T["Data"]>(initOrObject.initialSwapData);
195
+ }
196
+
197
+ this.lnurl = initOrObject.lnurl;
198
+ this.lnurlK1 = initOrObject.lnurlK1;
199
+ this.lnurlCallback = initOrObject.lnurlCallback;
200
+ this.prPosted = initOrObject.prPosted;
201
+
202
+ if(this._state===FromBTCLNSwapState.PR_CREATED && this._data!=null) {
203
+ this.initialSwapData = this._data;
204
+ delete this._data;
205
+ }
206
+ this.usesClaimHashAsId = initOrObject.usesClaimHashAsId ?? false;
207
+ }
208
+ this.tryRecomputeSwapPrice();
209
+ this.logger = getLogger("FromBTCLN("+this.getIdentifierHashString()+"): ");
210
+ }
211
+
212
+ /**
213
+ * @inheritDoc
214
+ * @internal
215
+ */
216
+ protected getSwapData(): T["Data"] {
217
+ return this._data ?? this.initialSwapData;
218
+ }
219
+
220
+ /**
221
+ * @inheritDoc
222
+ * @internal
223
+ */
224
+ protected upgradeVersion() {
225
+ if (this.version == null) {
226
+ switch (this._state) {
227
+ case -2:
228
+ this._state = FromBTCLNSwapState.QUOTE_EXPIRED;
229
+ break;
230
+ case -1:
231
+ this._state = FromBTCLNSwapState.FAILED;
232
+ break;
233
+ case 0:
234
+ this._state = FromBTCLNSwapState.PR_CREATED
235
+ break;
236
+ case 1:
237
+ this._state = FromBTCLNSwapState.PR_PAID
238
+ break;
239
+ case 2:
240
+ this._state = FromBTCLNSwapState.CLAIM_COMMITED
241
+ break;
242
+ case 3:
243
+ this._state = FromBTCLNSwapState.CLAIM_CLAIMED
244
+ break;
245
+ }
246
+ this.version = 1;
247
+ }
248
+ }
249
+
250
+ //////////////////////////////
251
+ //// Getters & utils
252
+
253
+ /**
254
+ * @inheritDoc
255
+ * @internal
256
+ */
257
+ protected getIdentifierHash(): Buffer {
258
+ const idBuffer: Buffer = this.usesClaimHashAsId
259
+ ? Buffer.from(this.getClaimHash(), "hex")
260
+ : this.getPaymentHash()!;
261
+ if(this._randomNonce==null) return idBuffer;
262
+ return Buffer.concat([idBuffer, Buffer.from(this._randomNonce, "hex")]);
263
+ }
264
+
265
+ /**
266
+ * Returns the payment hash of the swap and lightning network invoice, or `null` if not known (i.e. if
267
+ * the swap was recovered from on-chain data, the payment hash might not be known)
268
+ *
269
+ * @internal
270
+ */
271
+ protected getPaymentHash(): Buffer | null {
272
+ if(this.pr==null) return null;
273
+ if(this.pr.toLowerCase().startsWith("ln")) {
274
+ const parsed = bolt11Decode(this.pr);
275
+ if(parsed.tagsObject.payment_hash==null) throw new Error("Swap invoice has no payment hash field!");
276
+ return Buffer.from(parsed.tagsObject.payment_hash, "hex");
277
+ }
278
+ return Buffer.from(this.pr, "hex");
279
+ }
280
+
281
+ /**
282
+ * @inheritDoc
283
+ * @internal
284
+ */
285
+ protected canCommit(skipQuoteExpiryChecks?: boolean): boolean {
286
+ return this._state===FromBTCLNSwapState.PR_PAID || (!!skipQuoteExpiryChecks && this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED);
287
+ }
288
+
289
+ /**
290
+ * @inheritDoc
291
+ */
292
+ getInputAddress(): string | null {
293
+ return this.lnurl ?? this.pr ?? null;
294
+ }
295
+
296
+ /**
297
+ * @inheritDoc
298
+ */
299
+ getInputTxId(): string | null {
300
+ const paymentHash = this.getPaymentHash();
301
+ if(paymentHash==null) return null;
302
+ return paymentHash.toString("hex");
303
+ }
304
+
305
+ /**
306
+ * Returns the lightning network BOLT11 invoice that needs to be paid as an input to the swap.
307
+ *
308
+ * In case the swap is recovered from on-chain data, the address returned might be just a payment hash,
309
+ * as it is impossible to retrieve the actual lightning network invoice paid purely from on-chain
310
+ * data.
311
+ */
312
+ getAddress(): string {
313
+ return this.pr ?? "";
314
+ }
315
+
316
+ /**
317
+ * A hyperlink representation of the address + amount that the user needs to sends on the source chain.
318
+ * This is suitable to be displayed in a form of QR code.
319
+ *
320
+ * @remarks
321
+ * In case the swap is recovered from on-chain data, the address returned might be just a payment hash,
322
+ * as it is impossible to retrieve the actual lightning network invoice paid purely from on-chain
323
+ * data.
324
+ */
325
+ getHyperlink(): string {
326
+ return this.pr==null ? "" : "lightning:"+this.pr.toUpperCase();
327
+ }
328
+
329
+ /**
330
+ * Returns the timeout time (in UNIX milliseconds) when the swap will definitelly be considered as expired
331
+ * if the LP doesn't make it expired sooner
332
+ */
333
+ getDefinitiveExpiryTime(): number {
334
+ if(this.pr==null || !this.pr.toLowerCase().startsWith("ln")) return 0;
335
+ const decoded = bolt11Decode(this.pr);
336
+ if(decoded.timeExpireDate==null) throw new Error("Swap invoice doesn't contain expiry date field!");
337
+ const finalCltvExpiryDelta = decoded.tagsObject.min_final_cltv_expiry ?? 144;
338
+ const finalCltvExpiryDelay = finalCltvExpiryDelta * this.wrapper._options.bitcoinBlocktime * this.wrapper._options.safetyFactor;
339
+ return (decoded.timeExpireDate + finalCltvExpiryDelay)*1000;
340
+ }
341
+
342
+ /**
343
+ * Returns timeout time (in UNIX milliseconds) when the swap htlc will expire
344
+ */
345
+ getHtlcTimeoutTime(): number | null {
346
+ if(this._data==null) return null;
347
+ return Number(this.wrapper._getHtlcTimeout(this._data))*1000;
348
+ }
349
+
350
+ /**
351
+ * Returns timeout time (in UNIX milliseconds) when the LN invoice will expire
352
+ */
353
+ getTimeoutTime(): number {
354
+ if(this.pr==null || !this.pr.toLowerCase().startsWith("ln")) return 0;
355
+ const decoded = bolt11Decode(this.pr);
356
+ if(decoded.timeExpireDate==null) throw new Error("Swap invoice doesn't contain expiry date field!");
357
+ return (decoded.timeExpireDate*1000);
358
+ }
359
+
360
+ /**
361
+ * @inheritDoc
362
+ */
363
+ isFinished(): boolean {
364
+ return this._state===FromBTCLNSwapState.CLAIM_CLAIMED || this._state===FromBTCLNSwapState.QUOTE_EXPIRED || this._state===FromBTCLNSwapState.FAILED;
365
+ }
366
+
367
+ /**
368
+ * @inheritDoc
369
+ */
370
+ isClaimable(): boolean {
371
+ return this._state===FromBTCLNSwapState.CLAIM_COMMITED;
372
+ }
373
+
374
+ /**
375
+ * @inheritDoc
376
+ */
377
+ isSuccessful(): boolean {
378
+ return this._state===FromBTCLNSwapState.CLAIM_CLAIMED;
379
+ }
380
+
381
+ /**
382
+ * @inheritDoc
383
+ */
384
+ isFailed(): boolean {
385
+ return this._state===FromBTCLNSwapState.FAILED || this._state===FromBTCLNSwapState.EXPIRED;
386
+ }
387
+
388
+ /**
389
+ * @inheritDoc
390
+ */
391
+ isInProgress(): boolean {
392
+ return (this._state===FromBTCLNSwapState.PR_CREATED && this.initiated) ||
393
+ (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.initiated) ||
394
+ this._state===FromBTCLNSwapState.PR_PAID ||
395
+ this._state===FromBTCLNSwapState.CLAIM_COMMITED;
396
+ }
397
+
398
+ /**
399
+ * @inheritDoc
400
+ */
401
+ isQuoteExpired(): boolean {
402
+ return this._state===FromBTCLNSwapState.QUOTE_EXPIRED;
403
+ }
404
+
405
+ /**
406
+ * @inheritDoc
407
+ */
408
+ isQuoteSoftExpired(): boolean {
409
+ return this._state===FromBTCLNSwapState.QUOTE_EXPIRED || this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED;
410
+ }
411
+
412
+ /**
413
+ * @inheritDoc
414
+ * @internal
415
+ */
416
+ _verifyQuoteDefinitelyExpired(): Promise<boolean> {
417
+ if(this._state===FromBTCLNSwapState.PR_CREATED || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData==null)) {
418
+ return Promise.resolve(this.getDefinitiveExpiryTime()<Date.now());
419
+ }
420
+ return super._verifyQuoteDefinitelyExpired();
421
+ }
422
+
423
+ /**
424
+ * @inheritDoc
425
+ * @internal
426
+ */
427
+ _verifyQuoteValid(): Promise<boolean> {
428
+ if(
429
+ this._state===FromBTCLNSwapState.PR_CREATED ||
430
+ (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData==null)
431
+ ) {
432
+ return Promise.resolve(this.getTimeoutTime()>Date.now());
433
+ }
434
+ return super._verifyQuoteValid();
435
+ }
436
+
437
+
438
+ //////////////////////////////
439
+ //// Amounts & fees
440
+
441
+ /**
442
+ * @inheritDoc
443
+ */
444
+ getInputToken(): BtcToken<true> {
445
+ return BitcoinTokens.BTCLN;
446
+ }
447
+
448
+ /**
449
+ * @inheritDoc
450
+ */
451
+ getInput(): TokenAmount<BtcToken<true>> {
452
+ if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
453
+ return toTokenAmount(null, this.inputToken, this.wrapper._prices, this.pricingInfo);
454
+
455
+ const parsed = bolt11Decode(this.pr);
456
+ if(parsed.millisatoshis==null) throw new Error("Swap invoice doesn't contain msat amount field!");
457
+ const amount = (BigInt(parsed.millisatoshis) + 999n) / 1000n;
458
+ return toTokenAmount(amount, this.inputToken, this.wrapper._prices, this.pricingInfo);
459
+ }
460
+
461
+ /**
462
+ * @inheritDoc
463
+ */
464
+ getSmartChainNetworkFee(): Promise<TokenAmount<SCToken<T["ChainId"]>, true>> {
465
+ return this.getCommitAndClaimNetworkFee();
466
+ }
467
+
468
+ /**
469
+ * @inheritDoc
470
+ */
471
+ async hasEnoughForTxFees(): Promise<{
472
+ enoughBalance: boolean,
473
+ balance: TokenAmount<SCToken<T["ChainId"]>, true>,
474
+ required: TokenAmount<SCToken<T["ChainId"]>, true>
475
+ }> {
476
+ const [balance, feeRate] = await Promise.all([
477
+ this._contract.getBalance(this._getInitiator(), this.wrapper._chain.getNativeCurrencyAddress(), false),
478
+ this.feeRate!=null ? Promise.resolve<string>(this.feeRate) : this._contract.getInitFeeRate(
479
+ this.getSwapData().getOfferer(),
480
+ this.getSwapData().getClaimer(),
481
+ this.getSwapData().getToken(),
482
+ this.getSwapData().getClaimHash()
483
+ )
484
+ ]);
485
+ const commitFee = await this._contract.getCommitFee(this._getInitiator(), this.getSwapData(), feeRate);
486
+ const claimFee = await this._contract.getClaimFee(this._getInitiator(), this.getSwapData(), feeRate);
487
+ const totalFee = commitFee + claimFee + this.getSwapData().getTotalDeposit();
488
+ return {
489
+ enoughBalance: balance >= totalFee,
490
+ balance: toTokenAmount(balance, this.wrapper._getNativeToken(), this.wrapper._prices, this.pricingInfo),
491
+ required: toTokenAmount(totalFee, this.wrapper._getNativeToken(), this.wrapper._prices, this.pricingInfo)
492
+ };
493
+ }
494
+
495
+ private isValidSecretPreimage(secret: string) {
496
+ const paymentHash = Buffer.from(sha256(Buffer.from(secret, "hex")));
497
+ const claimHash = this._contract.getHashForHtlc(paymentHash).toString("hex");
498
+ return this.getSwapData().getClaimHash()===claimHash;
499
+ }
500
+
501
+ /**
502
+ * Sets the secret preimage for the swap, in case it is not known already
503
+ *
504
+ * @param secret Secret preimage that matches the expected payment hash
505
+ *
506
+ * @throws {Error} If an invalid secret preimage is provided
507
+ */
508
+ setSecretPreimage(secret: string) {
509
+ if(!this.isValidSecretPreimage(secret)) throw new Error("Invalid secret preimage provided, hash doesn't match!");
510
+ this.secret = secret;
511
+ }
512
+
513
+ /**
514
+ * Returns whether the secret preimage for this swap is known
515
+ */
516
+ hasSecretPreimage(): boolean {
517
+ return this.secret != null;
518
+ }
519
+
520
+
521
+ //////////////////////////////
522
+ //// Execution
523
+
524
+ /**
525
+ * Executes the swap with the provided bitcoin lightning network wallet or LNURL
526
+ *
527
+ * @param dstSigner Signer on the destination network, needs to have the same address as the one specified when
528
+ * quote was created, this is required for legacy swaps because the destination wallet needs to actively claim
529
+ * the swap funds on the destination (this also means you need native token to cover gas costs)
530
+ * @param walletOrLnurlWithdraw Bitcoin lightning wallet to use to pay the lightning network invoice, or an LNURL-withdraw
531
+ * link, wallet is not required and the LN invoice can be paid externally as well (just pass null or undefined here)
532
+ * @param callbacks Callbacks to track the progress of the swap
533
+ * @param options Optional options for the swap like feeRate, AbortSignal, and timeouts/intervals
534
+ * @param options.secret A swap secret to use for the claim transaction, generally only needed if the swap
535
+ * was recovered from on-chain data, or the pre-image was generated outside the SDK
536
+ */
537
+ async execute(
538
+ dstSigner: T["Signer"] | T["NativeSigner"],
539
+ walletOrLnurlWithdraw?: MinimalLightningNetworkWalletInterface | LNURLWithdraw | string | null | undefined,
540
+ callbacks?: {
541
+ onSourceTransactionReceived?: (sourceTxId: string) => void,
542
+ onDestinationCommitSent?: (destinationCommitTxId: string) => void,
543
+ onDestinationClaimSent?: (destinationClaimTxId: string) => void,
544
+ onSwapSettled?: (destinationTxId: string) => void
545
+ },
546
+ options?: {
547
+ abortSignal?: AbortSignal,
548
+ secret?: string,
549
+ lightningTxCheckIntervalSeconds?: number,
550
+ delayBetweenCommitAndClaimSeconds?: number
551
+ }
552
+ ): Promise<boolean> {
553
+ if(this._state===FromBTCLNSwapState.FAILED) throw new Error("Swap failed!");
554
+ if(this._state===FromBTCLNSwapState.EXPIRED) throw new Error("Swap HTLC expired!");
555
+ if(this._state===FromBTCLNSwapState.QUOTE_EXPIRED || this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED) throw new Error("Swap quote expired!");
556
+ if(this._state===FromBTCLNSwapState.CLAIM_CLAIMED) throw new Error("Swap already settled!");
557
+
558
+ let abortSignal = options?.abortSignal;
559
+
560
+ if(this._state===FromBTCLNSwapState.PR_CREATED) {
561
+ if(walletOrLnurlWithdraw!=null && this.lnurl==null) {
562
+ if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
563
+ throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
564
+
565
+ if(typeof(walletOrLnurlWithdraw)==="string" || isLNURLWithdraw(walletOrLnurlWithdraw)) {
566
+ await this.settleWithLNURLWithdraw(walletOrLnurlWithdraw);
567
+ } else {
568
+ const paymentPromise = walletOrLnurlWithdraw.payInvoice(this.pr);
569
+
570
+ const abortController = new AbortController();
571
+ paymentPromise.catch(e => abortController.abort(e));
572
+ if(options?.abortSignal!=null) options.abortSignal.addEventListener("abort", () => abortController.abort(options?.abortSignal?.reason));
573
+ abortSignal = abortController.signal;
574
+ }
575
+ }
576
+ const paymentSuccess = await this.waitForPayment(callbacks?.onSourceTransactionReceived, options?.lightningTxCheckIntervalSeconds, abortSignal);
577
+ if (!paymentSuccess) throw new Error("Failed to receive lightning network payment");
578
+ }
579
+
580
+ if(this._state===FromBTCLNSwapState.PR_PAID || this._state===FromBTCLNSwapState.CLAIM_COMMITED) {
581
+ if(this.canCommitAndClaimInOneShot()) {
582
+ await this.commitAndClaim(dstSigner, options?.abortSignal, undefined, callbacks?.onDestinationCommitSent, callbacks?.onDestinationClaimSent, options?.secret);
583
+ } else {
584
+ if(this._state===FromBTCLNSwapState.PR_PAID) {
585
+ await this.commit(dstSigner, options?.abortSignal, undefined, callbacks?.onDestinationCommitSent);
586
+ if(options?.delayBetweenCommitAndClaimSeconds!=null) await timeoutPromise(options.delayBetweenCommitAndClaimSeconds * 1000, options?.abortSignal);
587
+ }
588
+ if(this._state===FromBTCLNSwapState.CLAIM_COMMITED) {
589
+ await this.claim(dstSigner, options?.abortSignal, callbacks?.onDestinationClaimSent, options?.secret);
590
+ }
591
+ }
592
+ }
593
+
594
+ // @ts-ignore
595
+ if(this._state===FromBTCLNSwapState.CLAIM_CLAIMED) {
596
+ if(callbacks?.onSwapSettled!=null) callbacks.onSwapSettled(this.getOutputTxId()!);
597
+ }
598
+
599
+ return true;
600
+ }
601
+
602
+ /**
603
+ * @inheritDoc
604
+ *
605
+ * @param options
606
+ * @param options.skipChecks Skip checks like making sure init signature is still valid and swap
607
+ * wasn't commited yet (this is handled on swap creation, if you commit right after quoting, you
608
+ * can use `skipChecks=true`)
609
+ * @param options.secret A swap secret to use for the claim transaction, generally only needed if the swap
610
+ * was recovered from on-chain data, or the pre-image was generated outside the SDK
611
+ */
612
+ async txsExecute(options?: {
613
+ skipChecks?: boolean,
614
+ secret?: string
615
+ }) {
616
+ if(this._state===FromBTCLNSwapState.PR_CREATED) {
617
+ if(!await this._verifyQuoteValid()) throw new Error("Quote already expired or close to expiry!");
618
+ return [
619
+ {
620
+ name: "Payment" as const,
621
+ description: "Initiates the swap by paying up the lightning network invoice",
622
+ chain: "LIGHTNING" as const,
623
+ txs: [
624
+ {
625
+ type: "BOLT11_PAYMENT_REQUEST" as const,
626
+ address: this.getAddress(),
627
+ hyperlink: this.getHyperlink()
628
+ }
629
+ ]
630
+ }
631
+ ];
632
+ }
633
+
634
+ if(this._state===FromBTCLNSwapState.PR_PAID) {
635
+ if(!await this._verifyQuoteValid()) throw new Error("Quote already expired or close to expiry!");
636
+ const txsCommit = await this.txsCommit(options?.skipChecks);
637
+ const txsClaim = await this._txsClaim(undefined, options?.secret);
638
+ return [
639
+ {
640
+ name: "Commit" as const,
641
+ description: `Creates the HTLC escrow on the ${this.chainIdentifier} side`,
642
+ chain: this.chainIdentifier,
643
+ txs: txsCommit
644
+ },
645
+ {
646
+ name: "Claim" as const,
647
+ description: `Settles & claims the funds from the HTLC escrow on the ${this.chainIdentifier} side`,
648
+ chain: this.chainIdentifier,
649
+ txs: txsClaim
650
+ },
651
+ ];
652
+ }
653
+
654
+ if(this._state===FromBTCLNSwapState.CLAIM_COMMITED) {
655
+ const txsClaim = await this.txsClaim(undefined, options?.secret);
656
+ return [
657
+ {
658
+ name: "Claim" as const,
659
+ description: `Settles & claims the funds from the HTLC escrow on the ${this.chainIdentifier} side`,
660
+ chain: this.chainIdentifier,
661
+ txs: txsClaim
662
+ },
663
+ ];
664
+ }
665
+
666
+ throw new Error("Invalid swap state to obtain execution txns, required PR_CREATED, PR_PAID or CLAIM_COMMITED");
667
+ }
668
+
669
+ /**
670
+ * @inheritDoc
671
+ *
672
+ * @param options
673
+ * @param options.skipChecks Skip checks like making sure init signature is still valid and swap
674
+ * wasn't commited yet (this is handled on swap creation, if you commit right after quoting, you
675
+ * can use `skipChecks=true`)
676
+ * @param options.secret A swap secret to use for the claim transaction, generally only needed if the swap
677
+ * was recovered from on-chain data, or the pre-image was generated outside the SDK
678
+ */
679
+ async getCurrentActions(options?: {
680
+ skipChecks?: boolean,
681
+ secret?: string
682
+ }): Promise<SwapExecutionAction<T>[]> {
683
+ try {
684
+ return await this.txsExecute(options);
685
+ } catch (e) {
686
+ return [];
687
+ }
688
+ }
689
+
690
+
691
+ //////////////////////////////
692
+ //// Payment
693
+
694
+ /**
695
+ * Checks whether the LP received the LN payment and we can continue by committing & claiming the HTLC on-chain
696
+ *
697
+ * @param save If the new swap state should be saved
698
+ *
699
+ * @internal
700
+ */
701
+ async _checkIntermediaryPaymentReceived(save: boolean = true): Promise<boolean | null> {
702
+ if(
703
+ this._state===FromBTCLNSwapState.PR_PAID ||
704
+ this._state===FromBTCLNSwapState.CLAIM_COMMITED ||
705
+ this._state===FromBTCLNSwapState.CLAIM_CLAIMED ||
706
+ this._state===FromBTCLNSwapState.FAILED ||
707
+ this._state===FromBTCLNSwapState.EXPIRED
708
+ ) return true;
709
+ if(this._state===FromBTCLNSwapState.QUOTE_EXPIRED || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData!=null)) return false;
710
+ if(this.url==null) return false;
711
+
712
+ const paymentHash = this.getPaymentHash();
713
+ if(paymentHash==null)
714
+ throw new Error("Failed to check LP payment received, payment hash not known (probably recovered swap?)");
715
+
716
+ const resp = await IntermediaryAPI.getPaymentAuthorization(this.url, paymentHash.toString("hex"));
717
+ switch(resp.code) {
718
+ case PaymentAuthorizationResponseCodes.AUTH_DATA:
719
+ const data = new (this.wrapper._swapDataDeserializer(this._contractVersion))(resp.data.data);
720
+ try {
721
+ await this.checkIntermediaryReturnedAuthData(this._getInitiator(), data, resp.data);
722
+ this.expiry = await this._contract.getInitAuthorizationExpiry(
723
+ data,
724
+ resp.data
725
+ );
726
+ this._state = FromBTCLNSwapState.PR_PAID;
727
+ this._data = data;
728
+ this.signatureData = {
729
+ prefix: resp.data.prefix,
730
+ timeout: resp.data.timeout,
731
+ signature: resp.data.signature
732
+ };
733
+ this.initiated = true;
734
+ if(save) await this._saveAndEmit();
735
+ return true;
736
+ } catch (e) {}
737
+ return null;
738
+ case PaymentAuthorizationResponseCodes.EXPIRED:
739
+ this._state = FromBTCLNSwapState.QUOTE_EXPIRED;
740
+ this.initiated = true;
741
+ if(save) await this._saveAndEmit();
742
+ return false;
743
+ default:
744
+ return null;
745
+ }
746
+ }
747
+
748
+ /**
749
+ * Checks the data returned by the intermediary in the payment auth request
750
+ *
751
+ * @param signer Smart chain signer's address initiating the swap
752
+ * @param data Parsed swap data as returned by the intermediary
753
+ * @param signature Signature data as returned by the intermediary
754
+ *
755
+ * @throws {IntermediaryError} If the returned are not valid
756
+ * @throws {SignatureVerificationError} If the returned signature is not valid
757
+ * @throws {Error} If the swap is already committed on-chain
758
+ *
759
+ * @internal
760
+ */
761
+ protected async checkIntermediaryReturnedAuthData(signer: string, data: T["Data"], signature: SignatureData): Promise<void> {
762
+ data.setClaimer(signer);
763
+
764
+ if (data.getType() !== ChainSwapType.HTLC) throw new IntermediaryError("Invalid swap type");
765
+ if (!data.isOfferer(this.getSwapData().getOfferer())) throw new IntermediaryError("Invalid offerer used");
766
+ if (!data.isClaimer(this._getInitiator())) throw new IntermediaryError("Invalid claimer used");
767
+ if (!data.isToken(this.getSwapData().getToken())) throw new IntermediaryError("Invalid token used");
768
+ if (data.getSecurityDeposit() > this.getSwapData().getSecurityDeposit()) throw new IntermediaryError("Invalid security deposit!");
769
+ if (data.getClaimerBounty() !== 0n) throw new IntermediaryError("Invalid claimer bounty!");
770
+ if (data.getAmount() < this.getSwapData().getAmount()) throw new IntermediaryError("Invalid amount received!");
771
+ if (data.getClaimHash() !== this.getSwapData().getClaimHash()) throw new IntermediaryError("Invalid payment hash used!");
772
+ if (!data.isDepositToken(this.getSwapData().getDepositToken())) throw new IntermediaryError("Invalid deposit token used!");
773
+ if (data.hasSuccessAction()) throw new IntermediaryError("Invalid has success action");
774
+
775
+ await Promise.all([
776
+ this._contract.isValidInitAuthorization(this._getInitiator(), data, signature, this.feeRate),
777
+ this._contract.getCommitStatus(data.getClaimer(), data)
778
+ .then(status => {
779
+ if (status?.type !== SwapCommitStateType.NOT_COMMITED)
780
+ throw new Error("Swap already committed on-chain!");
781
+ })
782
+ ]);
783
+ }
784
+
785
+ /**
786
+ * Waits till a lightning network payment is received by the intermediary and client
787
+ * can continue by initiating (committing) & settling (claiming) the HTLC by calling
788
+ * either the {@link commitAndClaim} function (if the underlying chain allows commit
789
+ * and claim in a single transaction - check with {@link canCommitAndClaimInOneShot}).
790
+ * Or call {@link commit} and then {@link claim} separately.
791
+ *
792
+ * If this swap is using an LNURL-withdraw link as input, it automatically posts the
793
+ * generated invoice to the LNURL service to pay it.
794
+ *
795
+ * @param onPaymentReceived Callback as for when the LP reports having received the ln payment
796
+ * @param abortSignal Abort signal to stop waiting for payment
797
+ * @param checkIntervalSeconds How often to poll the intermediary for answer (default 5 seconds)
798
+ */
799
+ async waitForPayment(onPaymentReceived?: (txId: string) => void, checkIntervalSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
800
+ checkIntervalSeconds ??= 5;
801
+ if(
802
+ this._state!==FromBTCLNSwapState.PR_CREATED &&
803
+ (this._state!==FromBTCLNSwapState.QUOTE_SOFT_EXPIRED || this.signatureData!=null)
804
+ ) throw new Error("Must be in PR_CREATED state!");
805
+ if(this.url==null) throw new Error("LP URL not known, cannot await the payment!");
806
+
807
+ const abortController = new AbortController();
808
+ if(abortSignal!=null) abortSignal.addEventListener("abort", () => abortController.abort(abortSignal.reason));
809
+
810
+ let save = false;
811
+
812
+ if(this.lnurl!=null && this.lnurlK1!=null && this.lnurlCallback!=null && !this.prPosted) {
813
+ if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
814
+ throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
815
+
816
+ LNURL.postInvoiceToLNURLWithdraw({k1: this.lnurlK1, callback: this.lnurlCallback}, this.pr).catch(e => {
817
+ this.lnurlFailSignal.abort(e);
818
+ });
819
+ this.prPosted = true;
820
+ save ||= true;
821
+ }
822
+
823
+ if(!this.initiated) {
824
+ this.initiated = true;
825
+ save ||= true;
826
+ }
827
+
828
+ if(save) await this._saveAndEmit();
829
+
830
+ let lnurlFailListener = () => abortController.abort(this.lnurlFailSignal.signal.reason);
831
+ this.lnurlFailSignal.signal.addEventListener("abort", lnurlFailListener);
832
+ this.lnurlFailSignal.signal.throwIfAborted();
833
+
834
+ const paymentHash = this.getPaymentHash();
835
+ if(paymentHash==null)
836
+ throw new Error("Swap payment hash not available, the swap was probably recovered!");
837
+
838
+ let resp: PaymentAuthorizationResponse = {code: PaymentAuthorizationResponseCodes.PENDING, msg: ""};
839
+ while(!abortController.signal.aborted && resp.code===PaymentAuthorizationResponseCodes.PENDING) {
840
+ resp = await IntermediaryAPI.getPaymentAuthorization(this.url, paymentHash.toString("hex"));
841
+ if(resp.code===PaymentAuthorizationResponseCodes.PENDING)
842
+ await timeoutPromise(checkIntervalSeconds*1000, abortController.signal);
843
+ }
844
+ this.lnurlFailSignal.signal.removeEventListener("abort", lnurlFailListener);
845
+ abortController.signal.throwIfAborted();
846
+
847
+ if(resp.code===PaymentAuthorizationResponseCodes.AUTH_DATA) {
848
+ const sigData = resp.data;
849
+ const swapData = new (this.wrapper._swapDataDeserializer(this._contractVersion))(resp.data.data);
850
+ await this.checkIntermediaryReturnedAuthData(this._getInitiator(), swapData, sigData);
851
+ this.expiry = await this._contract.getInitAuthorizationExpiry(
852
+ swapData,
853
+ sigData
854
+ );
855
+ if(onPaymentReceived!=null) onPaymentReceived(this.getInputTxId()!);
856
+ if(this._state===FromBTCLNSwapState.PR_CREATED || this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED) {
857
+ this._data = swapData;
858
+ this.signatureData = {
859
+ prefix: sigData.prefix,
860
+ timeout: sigData.timeout,
861
+ signature: sigData.signature
862
+ };
863
+ await this._saveAndEmit(FromBTCLNSwapState.PR_PAID);
864
+ }
865
+ return true;
866
+ }
867
+
868
+ if(this._state===FromBTCLNSwapState.PR_CREATED || this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED) {
869
+ if(resp.code===PaymentAuthorizationResponseCodes.EXPIRED) {
870
+ await this._saveAndEmit(FromBTCLNSwapState.QUOTE_EXPIRED);
871
+ }
872
+
873
+ return false;
874
+ }
875
+
876
+ throw new IntermediaryError("Invalid response from the LP");
877
+ }
878
+
879
+
880
+ //////////////////////////////
881
+ //// Commit
882
+
883
+ /**
884
+ * @inheritDoc
885
+ *
886
+ * @throws {Error} If invalid signer is provided that doesn't match the swap data
887
+ */
888
+ async commit(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, skipChecks?: boolean, onBeforeTxSent?: (txId: string) => void): Promise<string> {
889
+ const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
890
+ this.checkSigner(signer);
891
+ let txCount = 0;
892
+ const txs = await this.txsCommit(skipChecks);
893
+ const result = await this.wrapper._chain.sendAndConfirm(
894
+ signer, txs, true, abortSignal, undefined, (txId: string) => {
895
+ txCount++;
896
+ if(onBeforeTxSent!=null && txCount===txs.length) onBeforeTxSent(txId);
897
+ return Promise.resolve();
898
+ }
899
+ );
900
+
901
+ this._commitTxId = result[result.length-1];
902
+ if(this._state===FromBTCLNSwapState.PR_PAID || this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED || this._state===FromBTCLNSwapState.QUOTE_EXPIRED) {
903
+ await this._saveAndEmit(FromBTCLNSwapState.CLAIM_COMMITED);
904
+ }
905
+ return this._commitTxId;
906
+ }
907
+
908
+ /**
909
+ * @inheritDoc
910
+ */
911
+ async waitTillCommited(abortSignal?: AbortSignal): Promise<void> {
912
+ if(this._state===FromBTCLNSwapState.CLAIM_COMMITED || this._state===FromBTCLNSwapState.CLAIM_CLAIMED) return Promise.resolve();
913
+ if(this._state!==FromBTCLNSwapState.PR_PAID && (this._state!==FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData!=null)) throw new Error("Invalid state");
914
+
915
+ const abortController = extendAbortController(abortSignal);
916
+ const result = await Promise.race([
917
+ this.watchdogWaitTillCommited(undefined, abortController.signal),
918
+ this.waitTillState(FromBTCLNSwapState.CLAIM_COMMITED, "gte", abortController.signal).then(() => 0)
919
+ ]);
920
+ abortController.abort();
921
+
922
+ if(result===0) this.logger.debug("waitTillCommited(): Resolved from state changed");
923
+ if(result===true) this.logger.debug("waitTillCommited(): Resolved from watchdog - commited");
924
+ if(result===false) {
925
+ this.logger.debug("waitTillCommited(): Resolved from watchdog - signature expired");
926
+ if(
927
+ this._state===FromBTCLNSwapState.PR_PAID ||
928
+ this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED
929
+ ) {
930
+ await this._saveAndEmit(FromBTCLNSwapState.QUOTE_EXPIRED);
931
+ }
932
+ return;
933
+ }
934
+
935
+ if(
936
+ this._state===FromBTCLNSwapState.PR_PAID ||
937
+ this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED
938
+ ) {
939
+ await this._saveAndEmit(FromBTCLNSwapState.CLAIM_COMMITED);
940
+ }
941
+ }
942
+
943
+
944
+ //////////////////////////////
945
+ //// Claim
946
+
947
+ /**
948
+ * Unsafe txs claim getter without state checking!
949
+ *
950
+ * @param _signer
951
+ * @param secret A swap secret to use for the claim transaction, generally only needed if the swap
952
+ * was recovered from on-chain data, or the pre-image was generated outside the SDK
953
+ *
954
+ * @internal
955
+ */
956
+ private async _txsClaim(_signer?: string | T["Signer"] | T["NativeSigner"], secret?: string): Promise<T["TX"][]> {
957
+ let address: string | undefined = undefined;
958
+ if(_signer!=null) {
959
+ if (typeof (_signer) === "string") {
960
+ address = _signer;
961
+ } else if (isAbstractSigner(_signer)) {
962
+ address = _signer.getAddress();
963
+ } else {
964
+ address = (await this.wrapper._chain.wrapSigner(_signer)).getAddress();
965
+ }
966
+ }
967
+
968
+ if(this._data==null) throw new Error("Unknown data, wrong state?");
969
+ const useSecret = secret ?? this.secret;
970
+ if(useSecret==null)
971
+ throw new Error("Swap secret pre-image not known and not provided, please provide the swap secret pre-image as an argument");
972
+ if(!this.isValidSecretPreimage(useSecret))
973
+ throw new Error("Invalid swap secret pre-image provided!");
974
+
975
+ return this._contract.txsClaimWithSecret(
976
+ address ?? this._getInitiator(),
977
+ this._data, useSecret, true, true
978
+ );
979
+ }
980
+
981
+ /**
982
+ * @inheritDoc
983
+ *
984
+ * @param _signer Optional signer address to use for claiming the swap, can also be different from the initializer
985
+ * @param secret A swap secret to use for the claim transaction, generally only needed if the swap
986
+ * was recovered from on-chain data, or the pre-image was generated outside the SDK
987
+ *
988
+ * @throws {Error} If in invalid state (must be {@link FromBTCLNSwapState.CLAIM_COMMITED})
989
+ */
990
+ async txsClaim(_signer?: string | T["Signer"] | T["NativeSigner"], secret?: string): Promise<T["TX"][]> {
991
+ if(this._state!==FromBTCLNSwapState.CLAIM_COMMITED) throw new Error("Must be in CLAIM_COMMITED state!");
992
+ return this._txsClaim(_signer, secret);
993
+ }
994
+
995
+ /**
996
+ * @inheritDoc
997
+ *
998
+ * @param _signer
999
+ * @param abortSignal
1000
+ * @param onBeforeTxSent
1001
+ * @param secret A swap secret to use for the claim transaction, generally only needed if the swap
1002
+ * was recovered from on-chain data, or the pre-image was generated outside the SDK
1003
+ */
1004
+ async claim(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, onBeforeTxSent?: (txId: string) => void, secret?: string): Promise<string> {
1005
+ const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
1006
+ let txCount = 0;
1007
+ const result = await this.wrapper._chain.sendAndConfirm(
1008
+ signer, await this.txsClaim(_signer, secret), true, abortSignal, undefined, (txId: string) => {
1009
+ txCount++;
1010
+ if(onBeforeTxSent!=null && txCount===1) onBeforeTxSent(txId);
1011
+ return Promise.resolve();
1012
+ }
1013
+ );
1014
+
1015
+ this._claimTxId = result[0];
1016
+ if(this._state===FromBTCLNSwapState.CLAIM_COMMITED || this._state===FromBTCLNSwapState.EXPIRED || this._state===FromBTCLNSwapState.FAILED) {
1017
+ await this._saveAndEmit(FromBTCLNSwapState.CLAIM_CLAIMED);
1018
+ }
1019
+ return result[0];
1020
+ }
1021
+
1022
+ /**
1023
+ * @inheritDoc
1024
+ *
1025
+ * @throws {Error} If swap is in invalid state (must be {@link FromBTCLNSwapState.CLAIM_COMMITED})
1026
+ * @throws {Error} If the LP refunded sooner than we were able to claim
1027
+ */
1028
+ async waitTillClaimed(maxWaitTimeSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
1029
+ if(this._state===FromBTCLNSwapState.CLAIM_CLAIMED) return Promise.resolve(true);
1030
+ if(this._state!==FromBTCLNSwapState.CLAIM_COMMITED) throw new Error("Invalid state (not CLAIM_COMMITED)");
1031
+
1032
+ const abortController = new AbortController();
1033
+ if(abortSignal!=null) abortSignal.addEventListener("abort", () => abortController.abort(abortSignal.reason));
1034
+ let timedOut: boolean = false;
1035
+ if(maxWaitTimeSeconds!=null) {
1036
+ const timeout = setTimeout(() => {
1037
+ timedOut = true;
1038
+ abortController.abort();
1039
+ }, maxWaitTimeSeconds * 1000);
1040
+ abortController.signal.addEventListener("abort", () => clearTimeout(timeout));
1041
+ }
1042
+
1043
+ let res: 0 | 1 | SwapCommitState;
1044
+ try {
1045
+ res = await Promise.race([
1046
+ this.watchdogWaitTillResult(undefined, abortController.signal),
1047
+ this.waitTillState(FromBTCLNSwapState.CLAIM_CLAIMED, "eq", abortController.signal).then(() => 0 as const),
1048
+ this.waitTillState(FromBTCLNSwapState.EXPIRED, "eq", abortController.signal).then(() => 1 as const),
1049
+ ]);
1050
+ abortController.abort();
1051
+ } catch (e) {
1052
+ abortController.abort();
1053
+ if(timedOut) return false;
1054
+ throw e;
1055
+ }
1056
+
1057
+ if(res===0) {
1058
+ this.logger.debug("waitTillClaimed(): Resolved from state change (CLAIM_CLAIMED)");
1059
+ return true;
1060
+ }
1061
+ if(res===1) {
1062
+ this.logger.debug("waitTillClaimed(): Resolved from state change (EXPIRED)");
1063
+ throw new Error("Swap expired during claiming");
1064
+ }
1065
+ this.logger.debug("waitTillClaimed(): Resolved from watchdog");
1066
+
1067
+ if(res?.type===SwapCommitStateType.PAID) {
1068
+ if((this._state as FromBTCLNSwapState)!==FromBTCLNSwapState.CLAIM_CLAIMED) {
1069
+ this._claimTxId = await res.getClaimTxId();
1070
+ await this._saveAndEmit(FromBTCLNSwapState.CLAIM_CLAIMED);
1071
+ }
1072
+ }
1073
+ if(res?.type===SwapCommitStateType.NOT_COMMITED || res?.type===SwapCommitStateType.EXPIRED) {
1074
+ if(
1075
+ (this._state as FromBTCLNSwapState)!==FromBTCLNSwapState.CLAIM_CLAIMED &&
1076
+ (this._state as FromBTCLNSwapState)!==FromBTCLNSwapState.FAILED
1077
+ ) {
1078
+ if(res.getRefundTxId!=null) this._refundTxId = await res.getRefundTxId();
1079
+ await this._saveAndEmit(FromBTCLNSwapState.FAILED);
1080
+ }
1081
+ throw new Error("Swap expired while waiting for claim!");
1082
+ }
1083
+ return true;
1084
+ }
1085
+
1086
+
1087
+ //////////////////////////////
1088
+ //// Commit & claim
1089
+
1090
+ /**
1091
+ * Estimated transaction fee for commit & claim transactions combined, required
1092
+ * to settle the swap on the smart chain destination side.
1093
+ */
1094
+ async getCommitAndClaimNetworkFee(): Promise<TokenAmount<SCToken<T["ChainId"]>, true>> {
1095
+ const swapContract: T["Contract"] = this._contract;
1096
+ const feeRate = this.feeRate ?? await swapContract.getInitFeeRate(
1097
+ this.getSwapData().getOfferer(),
1098
+ this.getSwapData().getClaimer(),
1099
+ this.getSwapData().getToken(),
1100
+ this.getSwapData().getClaimHash()
1101
+ );
1102
+ const commitFee = await (
1103
+ swapContract.getRawCommitFee!=null ?
1104
+ swapContract.getRawCommitFee(this._getInitiator(), this.getSwapData(), feeRate) :
1105
+ swapContract.getCommitFee(this._getInitiator(), this.getSwapData(), feeRate)
1106
+ );
1107
+ const claimFee = await (
1108
+ swapContract.getRawClaimFee!=null ?
1109
+ swapContract.getRawClaimFee(this._getInitiator(), this.getSwapData(), feeRate) :
1110
+ swapContract.getClaimFee(this._getInitiator(), this.getSwapData(), feeRate)
1111
+ );
1112
+
1113
+ return toTokenAmount(
1114
+ commitFee + claimFee,
1115
+ this.wrapper._getNativeToken(),
1116
+ this.wrapper._prices
1117
+ );
1118
+ }
1119
+
1120
+ /**
1121
+ * Returns whether the underlying chain supports calling commit and claim in a single call,
1122
+ * such that you can use the {@link commitAndClaim} function. If not you have to manually
1123
+ * call {@link commit} first and then {@link claim}.
1124
+ */
1125
+ canCommitAndClaimInOneShot(): boolean {
1126
+ return this._contract.initAndClaimWithSecret!=null;
1127
+ }
1128
+
1129
+ /**
1130
+ * Returns transactions for both commit & claim operation together, such that they can be signed all at once by
1131
+ * the wallet. **WARNING**: transactions must be sent sequentially, such that the claim (2nd) transaction is only
1132
+ * sent after the commit (1st) transaction confirms. Failure to do so can reveal the HTLC pre-image too soon,
1133
+ * opening a possibility for the LP to steal funds!
1134
+ *
1135
+ * @param skipChecks Skip checks like making sure init signature is still valid and swap wasn't commited yet
1136
+ * (this is handled when swap is created (quoted), if you commit right after quoting, you can use skipChecks=true)
1137
+ * @param secret A swap secret to use for the claim transaction, generally only needed if the swap
1138
+ * was recovered from on-chain data, or the pre-image was generated outside the SDK
1139
+ *
1140
+ * @throws {Error} If in invalid state (must be PR_PAID or CLAIM_COMMITED)
1141
+ */
1142
+ async txsCommitAndClaim(skipChecks?: boolean, secret?: string): Promise<T["TX"][]> {
1143
+ if(this._state===FromBTCLNSwapState.CLAIM_COMMITED) return await this.txsClaim(undefined, secret);
1144
+ if(
1145
+ this._state!==FromBTCLNSwapState.PR_PAID &&
1146
+ (this._state!==FromBTCLNSwapState.QUOTE_SOFT_EXPIRED || this.signatureData==null)
1147
+ ) throw new Error("Must be in PR_PAID state!");
1148
+ if(this._data==null) throw new Error("Unknown data, wrong state?");
1149
+
1150
+ const useSecret = secret ?? this.secret;
1151
+ if(useSecret==null)
1152
+ throw new Error("Swap secret pre-image not known and not provided, please provide the swap secret pre-image as second argument");
1153
+ if(!this.isValidSecretPreimage(useSecret))
1154
+ throw new Error("Invalid swap secret pre-image provided!");
1155
+
1156
+ const initTxs = await this.txsCommit(skipChecks);
1157
+ const claimTxs = await this._contract.txsClaimWithSecret(
1158
+ this._getInitiator(), this._data, useSecret,
1159
+ true, true
1160
+ );
1161
+
1162
+ return initTxs.concat(claimTxs);
1163
+ }
1164
+
1165
+ /**
1166
+ * Commits and claims the swap, in a way that the transactions can be signed together by the provided signer and
1167
+ * then automatically sent sequentially by the SDK. To check if the underlying chain supports this flow check
1168
+ * the {@link canCommitAndClaimInOneShot} function.
1169
+ *
1170
+ * @param _signer Signer to sign the transactions with, must be the same as used in the initialization
1171
+ * @param abortSignal Abort signal to stop waiting for the transaction confirmation and abort
1172
+ * @param skipChecks Skip checks like making sure init signature is still valid and swap wasn't commited yet
1173
+ * (this is handled when swap is created (quoted), if you commit right after quoting, you can use skipChecks=true)
1174
+ * @param onBeforeCommitTxSent Optional callback called before the initialization (commit) transaction is
1175
+ * broadcasted
1176
+ * @param onBeforeClaimTxSent Optional callback called before the settlement (claim) transaction is
1177
+ * broadcasted
1178
+ * @param secret A swap secret to use for the claim transaction, generally only needed if the swap
1179
+ * was recovered from on-chain data, or the pre-image was generated outside the SDK
1180
+ *
1181
+ * @throws {Error} If in invalid state (must be PR_PAID or CLAIM_COMMITED)
1182
+ * @throws {Error} If invalid signer is provided that doesn't match the swap data
1183
+ */
1184
+ async commitAndClaim(
1185
+ _signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, skipChecks?: boolean,
1186
+ onBeforeCommitTxSent?: (txId: string) => void, onBeforeClaimTxSent?: (txId: string) => void,
1187
+ secret?: string
1188
+ ): Promise<string[]> {
1189
+ const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
1190
+ if(!this.canCommitAndClaimInOneShot()) throw new Error("Cannot commitAndClaim in single action, please run commit and claim separately!");
1191
+ this.checkSigner(signer);
1192
+ if(this._state===FromBTCLNSwapState.CLAIM_COMMITED) return [await this.claim(signer, abortSignal, onBeforeClaimTxSent, secret)];
1193
+
1194
+ let txCount = 0;
1195
+ const txs = await this.txsCommitAndClaim(skipChecks, secret);
1196
+ const result = await this.wrapper._chain.sendAndConfirm(
1197
+ signer, txs, true, abortSignal, undefined, (txId: string) => {
1198
+ txCount++;
1199
+ if(onBeforeCommitTxSent!=null && txCount===1) onBeforeCommitTxSent(txId);
1200
+ if(onBeforeClaimTxSent!=null && txCount===txs.length) onBeforeClaimTxSent(txId);
1201
+ return Promise.resolve();
1202
+ }
1203
+ );
1204
+
1205
+ this._commitTxId = result[0] ?? this._commitTxId;
1206
+ this._claimTxId = result[result.length-1] ?? this._claimTxId;
1207
+ if(this._state!==FromBTCLNSwapState.CLAIM_CLAIMED) {
1208
+ await this._saveAndEmit(FromBTCLNSwapState.CLAIM_CLAIMED);
1209
+ }
1210
+
1211
+ return result;
1212
+ }
1213
+
1214
+
1215
+ //////////////////////////////
1216
+ //// LNURL
1217
+
1218
+ /**
1219
+ * Whether this swap uses an LNURL-withdraw link
1220
+ */
1221
+ isLNURL(): boolean {
1222
+ return this.lnurl!=null;
1223
+ }
1224
+
1225
+ /**
1226
+ * Gets the used LNURL or `null` if this is not an LNURL-withdraw swap
1227
+ */
1228
+ getLNURL(): string | null {
1229
+ return this.lnurl ?? null;
1230
+ }
1231
+
1232
+ /**
1233
+ * Pay the generated lightning network invoice with an LNURL-withdraw link, this
1234
+ * is useful when you want to display a lightning payment QR code and also want to
1235
+ * allow payments using LNURL-withdraw NFC cards.
1236
+ *
1237
+ * Note that the swap needs to be created **without** an LNURL to begin with for this function
1238
+ * to work. If this swap is already using an LNURL-withdraw link, this function throws.
1239
+ */
1240
+ async settleWithLNURLWithdraw(lnurl: string | LNURLWithdraw): Promise<void> {
1241
+ if(
1242
+ this._state!==FromBTCLNSwapState.PR_CREATED &&
1243
+ (this._state!==FromBTCLNSwapState.QUOTE_SOFT_EXPIRED || this.signatureData!=null)
1244
+ ) throw new Error("Must be in PR_CREATED state!");
1245
+
1246
+ if(this.lnurl!=null) throw new Error("Cannot settle LNURL-withdraw swap with different LNURL");
1247
+ let lnurlParams: LNURLWithdrawParamsWithUrl;
1248
+ if(typeof(lnurl)==="string") {
1249
+ const parsedLNURL = await LNURL.getLNURL(lnurl);
1250
+ if(parsedLNURL==null || parsedLNURL.tag!=="withdrawRequest")
1251
+ throw new UserError("Invalid LNURL-withdraw to settle the swap");
1252
+ lnurlParams = parsedLNURL;
1253
+ } else {
1254
+ lnurlParams = lnurl.params;
1255
+ }
1256
+
1257
+ if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
1258
+ throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
1259
+
1260
+ LNURL.useLNURLWithdraw(lnurlParams, this.pr).catch(e => this.lnurlFailSignal.abort(e));
1261
+ this.lnurl = lnurlParams.url;
1262
+ this.lnurlCallback = lnurlParams.callback;
1263
+ this.lnurlK1 = lnurlParams.k1;
1264
+ this.prPosted = true;
1265
+ await this._saveAndEmit();
1266
+ }
1267
+
1268
+
1269
+ //////////////////////////////
1270
+ //// Storage
1271
+
1272
+ /**
1273
+ * @inheritDoc
1274
+ */
1275
+ serialize(): any {
1276
+ return {
1277
+ ...super.serialize(),
1278
+ pr: this.pr,
1279
+ secret: this.secret,
1280
+ lnurl: this.lnurl,
1281
+ lnurlK1: this.lnurlK1,
1282
+ lnurlCallback: this.lnurlCallback,
1283
+ prPosted: this.prPosted,
1284
+ initialSwapData: this.initialSwapData.serialize(),
1285
+ usesClaimHashAsId: this.usesClaimHashAsId
1286
+ };
1287
+ }
1288
+
1289
+
1290
+ //////////////////////////////
1291
+ //// Swap ticks & sync
1292
+
1293
+ /**
1294
+ * Checks the swap's state on-chain and compares it to its internal state, updates/changes it according to on-chain
1295
+ * data
1296
+ *
1297
+ * @private
1298
+ */
1299
+ private async syncStateFromChain(quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState): Promise<boolean> {
1300
+ if(
1301
+ this._state===FromBTCLNSwapState.PR_PAID ||
1302
+ (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData!=null) ||
1303
+ this._state===FromBTCLNSwapState.CLAIM_COMMITED ||
1304
+ this._state===FromBTCLNSwapState.EXPIRED
1305
+ ) {
1306
+ //Check for expiry before the getCommitStatus to prevent race conditions
1307
+ let quoteExpired: boolean = false;
1308
+ if(this._state===FromBTCLNSwapState.PR_PAID || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData!=null)) {
1309
+ quoteExpired = quoteDefinitelyExpired ?? await this._verifyQuoteDefinitelyExpired();
1310
+ }
1311
+
1312
+ //Check if it's already successfully paid
1313
+ commitStatus ??= await this._contract.getCommitStatus(this._getInitiator(), this._data!);
1314
+ if(commitStatus!=null && await this._forciblySetOnchainState(commitStatus)) return true;
1315
+
1316
+ //Set the state on expiry here
1317
+ if(this._state===FromBTCLNSwapState.PR_PAID || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData!=null)) {
1318
+ if(quoteExpired) {
1319
+ this._state = FromBTCLNSwapState.QUOTE_EXPIRED;
1320
+ return true;
1321
+ }
1322
+ }
1323
+ }
1324
+ return false;
1325
+ }
1326
+
1327
+ /**
1328
+ * @inheritDoc
1329
+ * @internal
1330
+ */
1331
+ _shouldFetchExpiryStatus(): boolean {
1332
+ return this._state===FromBTCLNSwapState.PR_PAID || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData!=null);
1333
+ }
1334
+
1335
+ /**
1336
+ * @inheritDoc
1337
+ * @internal
1338
+ */
1339
+ _shouldFetchOnchainState(): boolean {
1340
+ return this._state===FromBTCLNSwapState.PR_PAID || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData!=null) ||
1341
+ this._state===FromBTCLNSwapState.CLAIM_COMMITED || this._state===FromBTCLNSwapState.EXPIRED;
1342
+ }
1343
+
1344
+ /**
1345
+ * Whether an intermediary (LP) should be contacted to get the state of this swap.
1346
+ *
1347
+ * @internal
1348
+ */
1349
+ _shouldCheckIntermediary(): boolean {
1350
+ return this._state===FromBTCLNSwapState.PR_CREATED || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData==null);
1351
+ }
1352
+
1353
+ /**
1354
+ * @inheritDoc
1355
+ * @internal
1356
+ */
1357
+ async _sync(save?: boolean, quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState, skipLpCheck?: boolean): Promise<boolean> {
1358
+ let changed = false;
1359
+
1360
+ if(this._state===FromBTCLNSwapState.PR_CREATED || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData==null)) {
1361
+ if(this._state!=FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.getTimeoutTime()<Date.now()) {
1362
+ this._state = FromBTCLNSwapState.QUOTE_SOFT_EXPIRED;
1363
+ changed ||= true;
1364
+ }
1365
+
1366
+ if(!skipLpCheck) try {
1367
+ const result = await this._checkIntermediaryPaymentReceived(false);
1368
+ if(result!==null) changed ||= true;
1369
+ } catch (e) {
1370
+ this.logger.error("_sync(): Failed to synchronize swap, error: ", e);
1371
+ }
1372
+
1373
+ if(this._state===FromBTCLNSwapState.PR_CREATED || (this._state===FromBTCLNSwapState.QUOTE_SOFT_EXPIRED && this.signatureData==null)) {
1374
+ if(await this._verifyQuoteDefinitelyExpired()) {
1375
+ this._state = FromBTCLNSwapState.QUOTE_EXPIRED;
1376
+ changed ||= true;
1377
+ }
1378
+ }
1379
+ }
1380
+
1381
+ if(await this.syncStateFromChain(quoteDefinitelyExpired, commitStatus)) changed = true;
1382
+
1383
+ if(this._state===FromBTCLNSwapState.CLAIM_COMMITED) {
1384
+ const expired = await this._contract.isExpired(this._getInitiator(), this._data!);
1385
+ if(expired) {
1386
+ this._state = FromBTCLNSwapState.EXPIRED;
1387
+ changed = true;
1388
+ }
1389
+ }
1390
+
1391
+ if(save && changed) await this._saveAndEmit();
1392
+
1393
+ return changed;
1394
+ }
1395
+
1396
+ /**
1397
+ * @inheritDoc
1398
+ * @internal
1399
+ */
1400
+ async _forciblySetOnchainState(commitStatus: SwapCommitState): Promise<boolean> {
1401
+ switch(commitStatus.type) {
1402
+ case SwapCommitStateType.PAID:
1403
+ if(this._claimTxId==null) this._claimTxId = await commitStatus.getClaimTxId();
1404
+ if(this.secret==null || this.pr==null) this._setSwapSecret(await commitStatus.getClaimResult());
1405
+ this._state = FromBTCLNSwapState.CLAIM_CLAIMED;
1406
+ return true;
1407
+ case SwapCommitStateType.NOT_COMMITED:
1408
+ if(this._refundTxId==null && commitStatus.getRefundTxId) this._refundTxId = await commitStatus.getRefundTxId();
1409
+ if(this._refundTxId!=null) {
1410
+ this._state = FromBTCLNSwapState.FAILED;
1411
+ return true;
1412
+ }
1413
+ break;
1414
+ case SwapCommitStateType.EXPIRED:
1415
+ if(this._refundTxId==null && commitStatus.getRefundTxId) this._refundTxId = await commitStatus.getRefundTxId();
1416
+ this._state = this._refundTxId==null ? FromBTCLNSwapState.QUOTE_EXPIRED : FromBTCLNSwapState.FAILED;
1417
+ return true;
1418
+ case SwapCommitStateType.COMMITED:
1419
+ if(this._state!==FromBTCLNSwapState.CLAIM_COMMITED && this._state!==FromBTCLNSwapState.EXPIRED) {
1420
+ this._state = FromBTCLNSwapState.CLAIM_COMMITED;
1421
+ return true;
1422
+ }
1423
+ break;
1424
+ }
1425
+ return false;
1426
+ }
1427
+
1428
+ /**
1429
+ * @inheritDoc
1430
+ * @internal
1431
+ */
1432
+ async _tick(save?: boolean): Promise<boolean> {
1433
+ switch(this._state) {
1434
+ case FromBTCLNSwapState.PR_CREATED:
1435
+ if(this.getTimeoutTime()<Date.now()) {
1436
+ this._state = FromBTCLNSwapState.QUOTE_SOFT_EXPIRED;
1437
+ if(save) await this._saveAndEmit();
1438
+ return true;
1439
+ }
1440
+ break;
1441
+ case FromBTCLNSwapState.PR_PAID:
1442
+ if(this.expiry<Date.now()) {
1443
+ this._state = FromBTCLNSwapState.QUOTE_SOFT_EXPIRED;
1444
+ if(save) await this._saveAndEmit();
1445
+ return true;
1446
+ }
1447
+ break;
1448
+ case FromBTCLNSwapState.CLAIM_COMMITED:
1449
+ const expired = await this._contract.isExpired(this._getInitiator(), this._data!);
1450
+ if(expired) {
1451
+ this._state = FromBTCLNSwapState.EXPIRED;
1452
+ if(save) await this._saveAndEmit();
1453
+ return true;
1454
+ }
1455
+ break;
1456
+ }
1457
+
1458
+ return false;
1459
+ }
1460
+
1461
+ /**
1462
+ * Forcibly sets the swap secret pre-image from on-chain data
1463
+ *
1464
+ * @internal
1465
+ */
1466
+ _setSwapSecret(secret: string) {
1467
+ this.secret = secret;
1468
+ if(this.pr==null) {
1469
+ this.pr = Buffer.from(sha256(Buffer.from(secret, "hex"))).toString("hex");
1470
+ }
1471
+ }
1472
+
1473
+ }