@atomiqlabs/sdk 8.9.1 → 8.9.3

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