@atomiqlabs/sdk 8.7.7 → 8.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +1760 -1760
  3. package/dist/SmartChainAssets.d.ts +181 -181
  4. package/dist/SmartChainAssets.js +181 -181
  5. package/dist/bitcoin/coinselect2/accumulative.d.ts +7 -6
  6. package/dist/bitcoin/coinselect2/accumulative.js +52 -52
  7. package/dist/bitcoin/coinselect2/blackjack.d.ts +7 -6
  8. package/dist/bitcoin/coinselect2/blackjack.js +38 -38
  9. package/dist/bitcoin/coinselect2/index.d.ts +20 -19
  10. package/dist/bitcoin/coinselect2/index.js +69 -69
  11. package/dist/bitcoin/coinselect2/utils.d.ts +82 -77
  12. package/dist/bitcoin/coinselect2/utils.js +158 -123
  13. package/dist/bitcoin/wallet/BitcoinWallet.d.ts +113 -130
  14. package/dist/bitcoin/wallet/BitcoinWallet.js +335 -322
  15. package/dist/bitcoin/wallet/IBitcoinWallet.d.ts +116 -78
  16. package/dist/bitcoin/wallet/IBitcoinWallet.js +21 -21
  17. package/dist/bitcoin/wallet/SingleAddressBitcoinWallet.d.ts +106 -101
  18. package/dist/bitcoin/wallet/SingleAddressBitcoinWallet.js +196 -190
  19. package/dist/enums/FeeType.d.ts +15 -15
  20. package/dist/enums/FeeType.js +19 -19
  21. package/dist/enums/SwapAmountType.d.ts +15 -15
  22. package/dist/enums/SwapAmountType.js +19 -19
  23. package/dist/enums/SwapDirection.d.ts +15 -15
  24. package/dist/enums/SwapDirection.js +19 -19
  25. package/dist/enums/SwapSide.d.ts +15 -15
  26. package/dist/enums/SwapSide.js +19 -19
  27. package/dist/enums/SwapType.d.ts +75 -75
  28. package/dist/enums/SwapType.js +79 -79
  29. package/dist/errors/IntermediaryError.d.ts +13 -13
  30. package/dist/errors/IntermediaryError.js +27 -27
  31. package/dist/errors/RequestError.d.ts +32 -32
  32. package/dist/errors/RequestError.js +54 -54
  33. package/dist/errors/UserError.d.ts +8 -8
  34. package/dist/errors/UserError.js +16 -16
  35. package/dist/events/UnifiedSwapEventListener.d.ts +23 -23
  36. package/dist/events/UnifiedSwapEventListener.js +132 -132
  37. package/dist/http/HttpUtils.d.ts +27 -27
  38. package/dist/http/HttpUtils.js +91 -91
  39. package/dist/http/paramcoders/IParamReader.d.ts +8 -8
  40. package/dist/http/paramcoders/IParamReader.js +2 -2
  41. package/dist/http/paramcoders/ParamDecoder.d.ts +44 -44
  42. package/dist/http/paramcoders/ParamDecoder.js +137 -137
  43. package/dist/http/paramcoders/ParamEncoder.d.ts +20 -20
  44. package/dist/http/paramcoders/ParamEncoder.js +36 -36
  45. package/dist/http/paramcoders/SchemaVerifier.d.ts +26 -26
  46. package/dist/http/paramcoders/SchemaVerifier.js +145 -145
  47. package/dist/http/paramcoders/client/ResponseParamDecoder.d.ts +11 -11
  48. package/dist/http/paramcoders/client/ResponseParamDecoder.js +57 -57
  49. package/dist/http/paramcoders/client/StreamParamEncoder.d.ts +13 -13
  50. package/dist/http/paramcoders/client/StreamParamEncoder.js +26 -26
  51. package/dist/http/paramcoders/client/StreamingFetchPromise.d.ts +16 -16
  52. package/dist/http/paramcoders/client/StreamingFetchPromise.js +174 -174
  53. package/dist/index.d.ts +85 -85
  54. package/dist/index.js +158 -158
  55. package/dist/intermediaries/Intermediary.d.ts +178 -178
  56. package/dist/intermediaries/Intermediary.js +166 -166
  57. package/dist/intermediaries/IntermediaryDiscovery.d.ts +211 -211
  58. package/dist/intermediaries/IntermediaryDiscovery.js +424 -424
  59. package/dist/intermediaries/apis/IntermediaryAPI.d.ts +450 -440
  60. package/dist/intermediaries/apis/IntermediaryAPI.js +618 -603
  61. package/dist/intermediaries/apis/TrustedIntermediaryAPI.d.ts +155 -155
  62. package/dist/intermediaries/apis/TrustedIntermediaryAPI.js +137 -137
  63. package/dist/lnurl/LNURL.d.ts +102 -102
  64. package/dist/lnurl/LNURL.js +321 -321
  65. package/dist/prices/RedundantSwapPrice.d.ts +110 -110
  66. package/dist/prices/RedundantSwapPrice.js +222 -222
  67. package/dist/prices/SingleSwapPrice.d.ts +34 -34
  68. package/dist/prices/SingleSwapPrice.js +44 -44
  69. package/dist/prices/SwapPriceWithChain.d.ts +107 -107
  70. package/dist/prices/SwapPriceWithChain.js +128 -128
  71. package/dist/prices/abstract/ICachedSwapPrice.d.ts +28 -28
  72. package/dist/prices/abstract/ICachedSwapPrice.js +62 -62
  73. package/dist/prices/abstract/IPriceProvider.d.ts +81 -81
  74. package/dist/prices/abstract/IPriceProvider.js +74 -74
  75. package/dist/prices/abstract/ISwapPrice.d.ts +168 -168
  76. package/dist/prices/abstract/ISwapPrice.js +279 -279
  77. package/dist/prices/providers/BinancePriceProvider.d.ts +23 -23
  78. package/dist/prices/providers/BinancePriceProvider.js +30 -30
  79. package/dist/prices/providers/CoinGeckoPriceProvider.d.ts +23 -23
  80. package/dist/prices/providers/CoinGeckoPriceProvider.js +29 -29
  81. package/dist/prices/providers/CoinPaprikaPriceProvider.d.ts +25 -25
  82. package/dist/prices/providers/CoinPaprikaPriceProvider.js +29 -29
  83. package/dist/prices/providers/CustomPriceProvider.d.ts +24 -24
  84. package/dist/prices/providers/CustomPriceProvider.js +35 -35
  85. package/dist/prices/providers/KrakenPriceProvider.d.ts +38 -38
  86. package/dist/prices/providers/KrakenPriceProvider.js +45 -45
  87. package/dist/prices/providers/OKXPriceProvider.d.ts +34 -34
  88. package/dist/prices/providers/OKXPriceProvider.js +29 -29
  89. package/dist/prices/providers/abstract/ExchangePriceProvider.d.ts +17 -17
  90. package/dist/prices/providers/abstract/ExchangePriceProvider.js +21 -21
  91. package/dist/prices/providers/abstract/HttpPriceProvider.d.ts +7 -7
  92. package/dist/prices/providers/abstract/HttpPriceProvider.js +12 -12
  93. package/dist/storage/IUnifiedStorage.d.ts +85 -85
  94. package/dist/storage/IUnifiedStorage.js +2 -2
  95. package/dist/storage/UnifiedSwapStorage.d.ts +114 -114
  96. package/dist/storage/UnifiedSwapStorage.js +116 -116
  97. package/dist/storage-browser/IndexedDBUnifiedStorage.d.ts +63 -63
  98. package/dist/storage-browser/IndexedDBUnifiedStorage.js +298 -298
  99. package/dist/storage-browser/LocalStorageManager.d.ts +49 -49
  100. package/dist/storage-browser/LocalStorageManager.js +93 -93
  101. package/dist/swapper/Swapper.d.ts +732 -692
  102. package/dist/swapper/Swapper.js +1713 -1657
  103. package/dist/swapper/SwapperFactory.d.ts +135 -135
  104. package/dist/swapper/SwapperFactory.js +162 -162
  105. package/dist/swapper/SwapperUtils.d.ts +206 -206
  106. package/dist/swapper/SwapperUtils.js +481 -481
  107. package/dist/swapper/SwapperWithChain.d.ts +404 -404
  108. package/dist/swapper/SwapperWithChain.js +469 -469
  109. package/dist/swapper/SwapperWithSigner.d.ts +322 -322
  110. package/dist/swapper/SwapperWithSigner.js +318 -318
  111. package/dist/swaps/IAddressSwap.d.ts +22 -22
  112. package/dist/swaps/IAddressSwap.js +14 -14
  113. package/dist/swaps/IBTCWalletSwap.d.ts +73 -73
  114. package/dist/swaps/IBTCWalletSwap.js +18 -18
  115. package/dist/swaps/IClaimableSwap.d.ts +49 -49
  116. package/dist/swaps/IClaimableSwap.js +15 -15
  117. package/dist/swaps/IClaimableSwapWrapper.d.ts +15 -15
  118. package/dist/swaps/IClaimableSwapWrapper.js +2 -2
  119. package/dist/swaps/IRefundableSwap.d.ts +43 -43
  120. package/dist/swaps/IRefundableSwap.js +14 -14
  121. package/dist/swaps/ISwap.d.ts +392 -392
  122. package/dist/swaps/ISwap.js +349 -349
  123. package/dist/swaps/ISwapWithGasDrop.d.ts +21 -21
  124. package/dist/swaps/ISwapWithGasDrop.js +12 -12
  125. package/dist/swaps/ISwapWrapper.d.ts +285 -285
  126. package/dist/swaps/ISwapWrapper.js +353 -353
  127. package/dist/swaps/escrow_swaps/IEscrowSelfInitSwap.d.ts +98 -98
  128. package/dist/swaps/escrow_swaps/IEscrowSelfInitSwap.js +126 -126
  129. package/dist/swaps/escrow_swaps/IEscrowSwap.d.ts +139 -139
  130. package/dist/swaps/escrow_swaps/IEscrowSwap.js +170 -170
  131. package/dist/swaps/escrow_swaps/IEscrowSwapWrapper.d.ts +128 -128
  132. package/dist/swaps/escrow_swaps/IEscrowSwapWrapper.js +167 -167
  133. package/dist/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.d.ts +105 -105
  134. package/dist/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.js +129 -129
  135. package/dist/swaps/escrow_swaps/frombtc/IFromBTCSelfInitSwap.d.ts +162 -162
  136. package/dist/swaps/escrow_swaps/frombtc/IFromBTCSelfInitSwap.js +190 -190
  137. package/dist/swaps/escrow_swaps/frombtc/IFromBTCWrapper.d.ts +64 -64
  138. package/dist/swaps/escrow_swaps/frombtc/IFromBTCWrapper.js +82 -82
  139. package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.d.ts +531 -531
  140. package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.js +1285 -1285
  141. package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.d.ts +190 -190
  142. package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.js +432 -432
  143. package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.d.ts +583 -583
  144. package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.js +1371 -1371
  145. package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.d.ts +235 -235
  146. package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.js +525 -525
  147. package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.d.ts +458 -458
  148. package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.js +1126 -1126
  149. package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.d.ts +202 -202
  150. package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.js +406 -406
  151. package/dist/swaps/escrow_swaps/tobtc/IToBTCSwap.d.ts +403 -403
  152. package/dist/swaps/escrow_swaps/tobtc/IToBTCSwap.js +924 -924
  153. package/dist/swaps/escrow_swaps/tobtc/IToBTCWrapper.d.ts +68 -68
  154. package/dist/swaps/escrow_swaps/tobtc/IToBTCWrapper.js +117 -117
  155. package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNSwap.d.ts +127 -127
  156. package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNSwap.js +256 -256
  157. package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.d.ts +251 -251
  158. package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.js +536 -536
  159. package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCSwap.d.ts +73 -73
  160. package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCSwap.js +155 -155
  161. package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.d.ts +132 -132
  162. package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.js +286 -286
  163. package/dist/swaps/spv_swaps/SpvFromBTCSwap.d.ts +637 -631
  164. package/dist/swaps/spv_swaps/SpvFromBTCSwap.js +1448 -1444
  165. package/dist/swaps/spv_swaps/SpvFromBTCWrapper.d.ts +257 -225
  166. package/dist/swaps/spv_swaps/SpvFromBTCWrapper.js +947 -822
  167. package/dist/swaps/trusted/ln/LnForGasSwap.d.ts +261 -261
  168. package/dist/swaps/trusted/ln/LnForGasSwap.js +511 -511
  169. package/dist/swaps/trusted/ln/LnForGasWrapper.d.ts +40 -40
  170. package/dist/swaps/trusted/ln/LnForGasWrapper.js +83 -83
  171. package/dist/swaps/trusted/onchain/OnchainForGasSwap.d.ts +342 -342
  172. package/dist/swaps/trusted/onchain/OnchainForGasSwap.js +715 -715
  173. package/dist/swaps/trusted/onchain/OnchainForGasWrapper.d.ts +69 -69
  174. package/dist/swaps/trusted/onchain/OnchainForGasWrapper.js +93 -93
  175. package/dist/types/AmountData.d.ts +10 -10
  176. package/dist/types/AmountData.js +2 -2
  177. package/dist/types/CustomPriceFunction.d.ts +11 -11
  178. package/dist/types/CustomPriceFunction.js +2 -2
  179. package/dist/types/PriceInfoType.d.ts +28 -28
  180. package/dist/types/PriceInfoType.js +57 -57
  181. package/dist/types/SwapExecutionAction.d.ts +88 -88
  182. package/dist/types/SwapExecutionAction.js +2 -2
  183. package/dist/types/SwapStateInfo.d.ts +5 -5
  184. package/dist/types/SwapStateInfo.js +2 -2
  185. package/dist/types/SwapWithSigner.d.ts +17 -17
  186. package/dist/types/SwapWithSigner.js +43 -43
  187. package/dist/types/Token.d.ts +99 -99
  188. package/dist/types/Token.js +76 -76
  189. package/dist/types/TokenAmount.d.ts +69 -69
  190. package/dist/types/TokenAmount.js +60 -60
  191. package/dist/types/fees/Fee.d.ts +50 -50
  192. package/dist/types/fees/Fee.js +2 -2
  193. package/dist/types/fees/FeeBreakdown.d.ts +11 -11
  194. package/dist/types/fees/FeeBreakdown.js +2 -2
  195. package/dist/types/fees/PercentagePPM.d.ts +17 -17
  196. package/dist/types/fees/PercentagePPM.js +18 -18
  197. package/dist/types/lnurl/LNURLPay.d.ts +61 -61
  198. package/dist/types/lnurl/LNURLPay.js +31 -31
  199. package/dist/types/lnurl/LNURLWithdraw.d.ts +48 -48
  200. package/dist/types/lnurl/LNURLWithdraw.js +27 -27
  201. package/dist/types/wallets/LightningInvoiceCreateService.d.ts +24 -24
  202. package/dist/types/wallets/LightningInvoiceCreateService.js +15 -15
  203. package/dist/types/wallets/MinimalBitcoinWalletInterface.d.ts +23 -23
  204. package/dist/types/wallets/MinimalBitcoinWalletInterface.js +2 -2
  205. package/dist/types/wallets/MinimalLightningNetworkWalletInterface.d.ts +9 -9
  206. package/dist/types/wallets/MinimalLightningNetworkWalletInterface.js +2 -2
  207. package/dist/utils/AutomaticClockDriftCorrection.d.ts +1 -1
  208. package/dist/utils/AutomaticClockDriftCorrection.js +70 -70
  209. package/dist/utils/BitcoinUtils.d.ts +16 -14
  210. package/dist/utils/BitcoinUtils.js +141 -102
  211. package/dist/utils/BitcoinWalletUtils.d.ts +7 -7
  212. package/dist/utils/BitcoinWalletUtils.js +14 -14
  213. package/dist/utils/Logger.d.ts +7 -7
  214. package/dist/utils/Logger.js +12 -12
  215. package/dist/utils/RetryUtils.d.ts +22 -22
  216. package/dist/utils/RetryUtils.js +67 -67
  217. package/dist/utils/SwapUtils.d.ts +88 -88
  218. package/dist/utils/SwapUtils.js +72 -72
  219. package/dist/utils/TimeoutUtils.d.ts +17 -17
  220. package/dist/utils/TimeoutUtils.js +55 -55
  221. package/dist/utils/TokenUtils.d.ts +19 -19
  222. package/dist/utils/TokenUtils.js +37 -37
  223. package/dist/utils/TypeUtils.d.ts +7 -7
  224. package/dist/utils/TypeUtils.js +2 -2
  225. package/dist/utils/Utils.d.ts +67 -67
  226. package/dist/utils/Utils.js +208 -208
  227. package/package.json +43 -43
  228. package/src/SmartChainAssets.ts +186 -186
  229. package/src/bitcoin/coinselect2/accumulative.ts +69 -68
  230. package/src/bitcoin/coinselect2/blackjack.ts +50 -49
  231. package/src/bitcoin/coinselect2/index.ts +93 -92
  232. package/src/bitcoin/coinselect2/utils.ts +236 -195
  233. package/src/bitcoin/wallet/BitcoinWallet.ts +439 -427
  234. package/src/bitcoin/wallet/IBitcoinWallet.ts +140 -99
  235. package/src/bitcoin/wallet/SingleAddressBitcoinWallet.ts +225 -217
  236. package/src/enums/FeeType.ts +15 -15
  237. package/src/enums/SwapAmountType.ts +16 -16
  238. package/src/enums/SwapDirection.ts +15 -15
  239. package/src/enums/SwapSide.ts +16 -16
  240. package/src/enums/SwapType.ts +75 -75
  241. package/src/errors/IntermediaryError.ts +28 -28
  242. package/src/errors/RequestError.ts +64 -64
  243. package/src/errors/UserError.ts +15 -15
  244. package/src/events/UnifiedSwapEventListener.ts +173 -173
  245. package/src/http/HttpUtils.ts +91 -91
  246. package/src/http/paramcoders/IParamReader.ts +9 -9
  247. package/src/http/paramcoders/ParamDecoder.ts +145 -145
  248. package/src/http/paramcoders/ParamEncoder.ts +40 -40
  249. package/src/http/paramcoders/SchemaVerifier.ts +153 -153
  250. package/src/http/paramcoders/client/ResponseParamDecoder.ts +57 -57
  251. package/src/http/paramcoders/client/StreamParamEncoder.ts +28 -28
  252. package/src/http/paramcoders/client/StreamingFetchPromise.ts +192 -192
  253. package/src/index.ts +140 -140
  254. package/src/intermediaries/Intermediary.ts +280 -280
  255. package/src/intermediaries/IntermediaryDiscovery.ts +541 -541
  256. package/src/intermediaries/apis/IntermediaryAPI.ts +963 -947
  257. package/src/intermediaries/apis/TrustedIntermediaryAPI.ts +257 -257
  258. package/src/lnurl/LNURL.ts +402 -402
  259. package/src/prices/RedundantSwapPrice.ts +264 -264
  260. package/src/prices/SingleSwapPrice.ts +50 -50
  261. package/src/prices/SwapPriceWithChain.ts +194 -194
  262. package/src/prices/abstract/ICachedSwapPrice.ts +85 -85
  263. package/src/prices/abstract/IPriceProvider.ts +127 -127
  264. package/src/prices/abstract/ISwapPrice.ts +390 -390
  265. package/src/prices/providers/BinancePriceProvider.ts +48 -48
  266. package/src/prices/providers/CoinGeckoPriceProvider.ts +46 -46
  267. package/src/prices/providers/CoinPaprikaPriceProvider.ts +49 -49
  268. package/src/prices/providers/CustomPriceProvider.ts +40 -40
  269. package/src/prices/providers/KrakenPriceProvider.ts +83 -83
  270. package/src/prices/providers/OKXPriceProvider.ts +59 -59
  271. package/src/prices/providers/abstract/ExchangePriceProvider.ts +31 -31
  272. package/src/prices/providers/abstract/HttpPriceProvider.ts +14 -14
  273. package/src/storage/IUnifiedStorage.ts +95 -95
  274. package/src/storage/UnifiedSwapStorage.ts +141 -141
  275. package/src/storage-browser/IndexedDBUnifiedStorage.ts +350 -350
  276. package/src/storage-browser/LocalStorageManager.ts +106 -106
  277. package/src/swapper/Swapper.ts +2488 -2416
  278. package/src/swapper/SwapperFactory.ts +307 -307
  279. package/src/swapper/SwapperUtils.ts +570 -570
  280. package/src/swapper/SwapperWithChain.ts +707 -707
  281. package/src/swapper/SwapperWithSigner.ts +511 -511
  282. package/src/swaps/IAddressSwap.ts +30 -30
  283. package/src/swaps/IBTCWalletSwap.ts +92 -92
  284. package/src/swaps/IClaimableSwap.ts +65 -65
  285. package/src/swaps/IClaimableSwapWrapper.ts +17 -17
  286. package/src/swaps/IRefundableSwap.ts +58 -58
  287. package/src/swaps/ISwap.ts +703 -703
  288. package/src/swaps/ISwapWithGasDrop.ts +25 -25
  289. package/src/swaps/ISwapWrapper.ts +539 -539
  290. package/src/swaps/escrow_swaps/IEscrowSelfInitSwap.ts +217 -217
  291. package/src/swaps/escrow_swaps/IEscrowSwap.ts +269 -269
  292. package/src/swaps/escrow_swaps/IEscrowSwapWrapper.ts +282 -282
  293. package/src/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.ts +169 -169
  294. package/src/swaps/escrow_swaps/frombtc/IFromBTCSelfInitSwap.ts +300 -300
  295. package/src/swaps/escrow_swaps/frombtc/IFromBTCWrapper.ts +107 -107
  296. package/src/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.ts +1473 -1474
  297. package/src/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.ts +601 -601
  298. package/src/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.ts +1582 -1582
  299. package/src/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.ts +750 -750
  300. package/src/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.ts +1299 -1299
  301. package/src/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.ts +610 -610
  302. package/src/swaps/escrow_swaps/tobtc/IToBTCSwap.ts +1096 -1096
  303. package/src/swaps/escrow_swaps/tobtc/IToBTCWrapper.ts +138 -138
  304. package/src/swaps/escrow_swaps/tobtc/ln/ToBTCLNSwap.ts +304 -304
  305. package/src/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.ts +786 -786
  306. package/src/swaps/escrow_swaps/tobtc/onchain/ToBTCSwap.ts +206 -206
  307. package/src/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.ts +401 -401
  308. package/src/swaps/spv_swaps/SpvFromBTCSwap.ts +1812 -1799
  309. package/src/swaps/spv_swaps/SpvFromBTCWrapper.ts +1236 -1060
  310. package/src/swaps/trusted/ln/LnForGasSwap.ts +589 -589
  311. package/src/swaps/trusted/ln/LnForGasWrapper.ts +91 -91
  312. package/src/swaps/trusted/onchain/OnchainForGasSwap.ts +862 -862
  313. package/src/swaps/trusted/onchain/OnchainForGasWrapper.ts +131 -131
  314. package/src/types/AmountData.ts +9 -9
  315. package/src/types/CustomPriceFunction.ts +11 -11
  316. package/src/types/PriceInfoType.ts +66 -66
  317. package/src/types/SwapExecutionAction.ts +99 -99
  318. package/src/types/SwapStateInfo.ts +6 -6
  319. package/src/types/SwapWithSigner.ts +61 -61
  320. package/src/types/Token.ts +163 -163
  321. package/src/types/TokenAmount.ts +132 -132
  322. package/src/types/fees/Fee.ts +56 -56
  323. package/src/types/fees/FeeBreakdown.ts +11 -11
  324. package/src/types/fees/PercentagePPM.ts +26 -26
  325. package/src/types/lnurl/LNURLPay.ts +79 -79
  326. package/src/types/lnurl/LNURLWithdraw.ts +61 -61
  327. package/src/types/wallets/LightningInvoiceCreateService.ts +30 -30
  328. package/src/types/wallets/MinimalBitcoinWalletInterface.ts +21 -21
  329. package/src/types/wallets/MinimalLightningNetworkWalletInterface.ts +9 -9
  330. package/src/utils/AutomaticClockDriftCorrection.ts +71 -71
  331. package/src/utils/BitcoinUtils.ts +132 -91
  332. package/src/utils/BitcoinWalletUtils.ts +15 -15
  333. package/src/utils/Logger.ts +14 -14
  334. package/src/utils/RetryUtils.ts +78 -78
  335. package/src/utils/SwapUtils.ts +99 -99
  336. package/src/utils/TimeoutUtils.ts +49 -49
  337. package/src/utils/TokenUtils.ts +33 -33
  338. package/src/utils/TypeUtils.ts +8 -8
  339. package/src/utils/Utils.ts +212 -212
@@ -1,1799 +1,1812 @@
1
- import {isISwapInit, ISwap, ISwapInit} from "../ISwap";
2
- import {
3
- BtcTxWithBlockheight,
4
- ChainType,
5
- isAbstractSigner,
6
- SpvWithdrawalClaimedState,
7
- SpvWithdrawalClosedState,
8
- SpvWithdrawalFrontedState,
9
- SpvWithdrawalState,
10
- SpvWithdrawalStateType
11
- } from "@atomiqlabs/base";
12
- import {SwapType} from "../../enums/SwapType";
13
- import {SpvFromBTCTypeDefinition, SpvFromBTCWrapper} from "./SpvFromBTCWrapper";
14
- import {extendAbortController} from "../../utils/Utils";
15
- import {parsePsbtTransaction, toCoinselectAddressType, toOutputScript} from "../../utils/BitcoinUtils";
16
- import {getInputType, Transaction} from "@scure/btc-signer";
17
- import {Buffer} from "buffer";
18
- import {Fee} from "../../types/fees/Fee";
19
- import {IBitcoinWallet, isIBitcoinWallet} from "../../bitcoin/wallet/IBitcoinWallet";
20
- import {IntermediaryAPI} from "../../intermediaries/apis/IntermediaryAPI";
21
- import {IBTCWalletSwap} from "../IBTCWalletSwap";
22
- import {ISwapWithGasDrop} from "../ISwapWithGasDrop";
23
- import {
24
- MinimalBitcoinWalletInterface,
25
- MinimalBitcoinWalletInterfaceWithSigner
26
- } from "../../types/wallets/MinimalBitcoinWalletInterface";
27
- import {IClaimableSwap} from "../IClaimableSwap";
28
- import {FeeType} from "../../enums/FeeType";
29
- import {ppmToPercentage} from "../../types/fees/PercentagePPM";
30
- import {TokenAmount, toTokenAmount} from "../../types/TokenAmount";
31
- import {BitcoinTokens, BtcToken, SCToken} from "../../types/Token";
32
- import {getLogger, LoggerType} from "../../utils/Logger";
33
- import {timeoutPromise} from "../../utils/TimeoutUtils";
34
- import {
35
- deserializePriceInfoType,
36
- isPriceInfoType,
37
- PriceInfoType,
38
- serializePriceInfoType
39
- } from "../../types/PriceInfoType";
40
- import {toBitcoinWallet} from "../../utils/BitcoinWalletUtils";
41
- import {SwapExecutionAction, SwapExecutionActionBitcoin} from "../../types/SwapExecutionAction";
42
-
43
- /**
44
- * State enum for SPV vault (UTXO-controlled vault) based swaps
45
- * @category Swaps/Bitcoin → Smart chain
46
- */
47
- export enum SpvFromBTCSwapState {
48
- /**
49
- * Catastrophic failure has occurred when processing the swap on the smart chain side,
50
- * this implies a bug in the smart contract code or the user and intermediary deliberately
51
- * creating a bitcoin transaction with invalid format unparsable by the smart contract.
52
- */
53
- CLOSED = -5,
54
- /**
55
- * Some of the bitcoin swap transaction inputs were double-spent, this means the swap
56
- * has failed and no BTC was sent
57
- */
58
- FAILED = -4,
59
- /**
60
- * The intermediary (LP) declined to co-sign the submitted PSBT, hence the swap failed
61
- */
62
- DECLINED = -3,
63
- /**
64
- * Swap has expired for good and there is no way how it can be executed anymore
65
- */
66
- QUOTE_EXPIRED = -2,
67
- /**
68
- * A swap is almost expired, and it should be presented to the user as expired, though
69
- * there is still a chance that it will be processed
70
- */
71
- QUOTE_SOFT_EXPIRED = -1,
72
- /**
73
- * Swap was created, use the {@link SpvFromBTCSwap.getFundedPsbt} or {@link SpvFromBTCSwap.getPsbt} functions
74
- * to get the bitcoin swap PSBT that should be signed by the user's wallet and then submitted via the
75
- * {@link SpvFromBTCSwap.submitPsbt} function.
76
- */
77
- CREATED = 0,
78
- /**
79
- * Swap bitcoin PSBT was submitted by the client to the SDK
80
- */
81
- SIGNED = 1,
82
- /**
83
- * Swap bitcoin PSBT sent to the intermediary (LP), waiting for the intermediary co-sign
84
- * it and broadcast. You can use the {@link SpvFromBTCSwap.waitTillClaimedOrFronted}
85
- * function to wait till the intermediary broadcasts the transaction and the transaction
86
- * confirms.
87
- */
88
- POSTED = 2,
89
- /**
90
- * Intermediary (LP) has co-signed and broadcasted the bitcoin transaction. You can use the
91
- * {@link SpvFromBTCSwap.waitTillClaimedOrFronted} function to wait till the transaction
92
- * confirms.
93
- */
94
- BROADCASTED = 3,
95
- /**
96
- * Settlement on the destination smart chain was fronted and funds were already received
97
- * by the user, even before the final settlement.
98
- */
99
- FRONTED = 4,
100
- /**
101
- * Bitcoin transaction confirmed with necessary amount of confirmations, wait for automatic
102
- * settlement by the watchtower with the {@link waitTillClaimedOrFronted} function, or settle manually
103
- * using the {@link FromBTCSwap.claim} or {@link FromBTCSwap.txsClaim} function.
104
- */
105
- BTC_TX_CONFIRMED = 5,
106
- /**
107
- * Swap settled on the smart chain and funds received
108
- */
109
- CLAIMED = 6
110
- }
111
-
112
- const SpvFromBTCSwapStateDescription = {
113
- [SpvFromBTCSwapState.CLOSED]: "Catastrophic failure has occurred when processing the swap on the smart chain side, this implies a bug in the smart contract code or the user and intermediary deliberately creating a bitcoin transaction with invalid format unparsable by the smart contract.",
114
- [SpvFromBTCSwapState.FAILED]: "Some of the bitcoin swap transaction inputs were double-spent, this means the swap has failed and no BTC was sent",
115
- [SpvFromBTCSwapState.DECLINED]: "The intermediary (LP) declined to co-sign the submitted PSBT, hence the swap failed",
116
- [SpvFromBTCSwapState.QUOTE_EXPIRED]: "Swap has expired for good and there is no way how it can be executed anymore",
117
- [SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED]: "A swap is almost expired, and it should be presented to the user as expired, though there is still a chance that it will be processed",
118
- [SpvFromBTCSwapState.CREATED]: "Swap was created, get the bitcoin swap PSBT that should be signed by the user's wallet and then submit it back to the SDK.",
119
- [SpvFromBTCSwapState.SIGNED]: "Swap bitcoin PSBT was submitted by the client to the SDK",
120
- [SpvFromBTCSwapState.POSTED]: "Swap bitcoin PSBT sent to the intermediary (LP), waiting for the intermediary co-sign it and broadcast.",
121
- [SpvFromBTCSwapState.BROADCASTED]: "Intermediary (LP) has co-signed and broadcasted the bitcoin transaction.",
122
- [SpvFromBTCSwapState.FRONTED]: "Settlement on the destination smart chain was fronted and funds were already received by the user, even before the final settlement.",
123
- [SpvFromBTCSwapState.BTC_TX_CONFIRMED]: "Bitcoin transaction confirmed with necessary amount of confirmations, wait for automatic settlement by the watchtower or settle manually.",
124
- [SpvFromBTCSwapState.CLAIMED]: "Swap settled on the smart chain and funds received"
125
- }
126
-
127
- export type SpvFromBTCSwapInit = ISwapInit & {
128
- quoteId: string;
129
- recipient: string;
130
- vaultOwner: string;
131
- vaultId: bigint;
132
- vaultRequiredConfirmations: number;
133
- vaultTokenMultipliers: bigint[];
134
- vaultBtcAddress: string;
135
- vaultUtxo: string;
136
- vaultUtxoValue: bigint;
137
- btcDestinationAddress: string;
138
- btcAmount: bigint;
139
- btcAmountSwap: bigint;
140
- btcAmountGas: bigint;
141
- minimumBtcFeeRate: number;
142
- outputTotalSwap: bigint;
143
- outputSwapToken: string;
144
- outputTotalGas: bigint;
145
- outputGasToken: string;
146
- gasSwapFeeBtc: bigint;
147
- gasSwapFee: bigint;
148
- callerFeeShare: bigint;
149
- frontingFeeShare: bigint;
150
- executionFeeShare: bigint;
151
- genesisSmartChainBlockHeight: number;
152
- gasPricingInfo?: PriceInfoType;
153
- };
154
-
155
- export function isSpvFromBTCSwapInit(obj: any): obj is SpvFromBTCSwapInit {
156
- return typeof obj === "object" &&
157
- typeof(obj.quoteId)==="string" &&
158
- typeof(obj.recipient)==="string" &&
159
- typeof(obj.vaultOwner)==="string" &&
160
- typeof(obj.vaultId)==="bigint" &&
161
- typeof(obj.vaultRequiredConfirmations)==="number" &&
162
- Array.isArray(obj.vaultTokenMultipliers) && obj.vaultTokenMultipliers.reduce((prev: boolean, curr: any) => prev && typeof(curr)==="bigint", true) &&
163
- typeof(obj.vaultBtcAddress)==="string" &&
164
- typeof(obj.vaultUtxo)==="string" &&
165
- typeof(obj.vaultUtxoValue)==="bigint" &&
166
- typeof(obj.btcDestinationAddress)==="string" &&
167
- typeof(obj.btcAmount)==="bigint" &&
168
- typeof(obj.btcAmountSwap)==="bigint" &&
169
- typeof(obj.btcAmountGas)==="bigint" &&
170
- typeof(obj.minimumBtcFeeRate)==="number" &&
171
- typeof(obj.outputTotalSwap)==="bigint" &&
172
- typeof(obj.outputSwapToken)==="string" &&
173
- typeof(obj.outputTotalGas)==="bigint" &&
174
- typeof(obj.outputGasToken)==="string" &&
175
- typeof(obj.gasSwapFeeBtc)==="bigint" &&
176
- typeof(obj.gasSwapFee)==="bigint" &&
177
- typeof(obj.callerFeeShare)==="bigint" &&
178
- typeof(obj.frontingFeeShare)==="bigint" &&
179
- typeof(obj.executionFeeShare)==="bigint" &&
180
- typeof(obj.genesisSmartChainBlockHeight)==="number" &&
181
- (obj.gasPricingInfo==null || isPriceInfoType(obj.gasPricingInfo)) &&
182
- isISwapInit(obj);
183
- }
184
-
185
- /**
186
- * New spv vault (UTXO-controlled vault) based swaps for Bitcoin -> Smart chain swaps not requiring
187
- * any initiation on the destination chain, and with the added possibility for the user to receive
188
- * a native token on the destination chain as part of the swap (a "gas drop" feature).
189
- *
190
- * @category Swaps/Bitcoin → Smart chain
191
- */
192
- export class SpvFromBTCSwap<T extends ChainType>
193
- extends ISwap<T, SpvFromBTCTypeDefinition<T>>
194
- implements IBTCWalletSwap, ISwapWithGasDrop<T>, IClaimableSwap<T, SpvFromBTCTypeDefinition<T>, SpvFromBTCSwapState> {
195
-
196
- protected readonly currentVersion: number = 2;
197
-
198
- readonly TYPE: SwapType.SPV_VAULT_FROM_BTC = SwapType.SPV_VAULT_FROM_BTC;
199
-
200
- /**
201
- * @internal
202
- */
203
- protected readonly swapStateDescription = SpvFromBTCSwapStateDescription;
204
- /**
205
- * @internal
206
- */
207
- protected readonly swapStateName = (state: number) => SpvFromBTCSwapState[state];
208
- /**
209
- * @inheritDoc
210
- * @internal
211
- */
212
- protected readonly logger: LoggerType;
213
-
214
- private readonly quoteId: string;
215
- private readonly recipient: string;
216
-
217
- private readonly vaultOwner: string;
218
- private readonly vaultId: bigint;
219
- private readonly vaultRequiredConfirmations: number;
220
- private readonly vaultTokenMultipliers: bigint[];
221
-
222
- private readonly vaultBtcAddress: string;
223
- private readonly vaultUtxo: string;
224
- private readonly vaultUtxoValue: bigint;
225
-
226
- private readonly btcDestinationAddress: string;
227
- private readonly btcAmount: bigint;
228
- private readonly btcAmountSwap: bigint;
229
- private readonly btcAmountGas: bigint;
230
-
231
- private readonly outputTotalSwap: bigint;
232
- private readonly outputSwapToken: string;
233
- private readonly outputTotalGas: bigint;
234
- private readonly outputGasToken: string;
235
-
236
- private readonly gasSwapFeeBtc: bigint;
237
- private readonly gasSwapFee: bigint;
238
-
239
- private readonly callerFeeShare: bigint;
240
- private readonly frontingFeeShare: bigint;
241
- private readonly executionFeeShare: bigint;
242
-
243
- private readonly gasPricingInfo?: PriceInfoType;
244
-
245
- private posted?: boolean;
246
-
247
- /**
248
- * @internal
249
- */
250
- readonly _genesisSmartChainBlockHeight: number;
251
- /**
252
- * @internal
253
- */
254
- _senderAddress?: string;
255
- /**
256
- * @internal
257
- */
258
- _claimTxId?: string;
259
- /**
260
- * @internal
261
- */
262
- _frontTxId?: string;
263
- /**
264
- * @internal
265
- */
266
- _data?: T["SpvVaultWithdrawalData"];
267
-
268
- /**
269
- * Minimum fee rate in sats/vB that the input bitcoin transaction needs to pay
270
- */
271
- readonly minimumBtcFeeRate: number;
272
-
273
- /**
274
- * Time at which the SDK realized the bitcoin transaction was confirmed
275
- * @private
276
- */
277
- private btcTxConfirmedAt?: number;
278
-
279
- private _contract: T["SpvVaultContract"];
280
-
281
- constructor(wrapper: SpvFromBTCWrapper<T>, init: SpvFromBTCSwapInit);
282
- constructor(wrapper: SpvFromBTCWrapper<T>, obj: any);
283
- constructor(wrapper: SpvFromBTCWrapper<T>, initOrObject: SpvFromBTCSwapInit | any) {
284
- if(isSpvFromBTCSwapInit(initOrObject) && initOrObject.url!=null) initOrObject.url += "/frombtc_spv";
285
- super(wrapper, initOrObject);
286
- if(isSpvFromBTCSwapInit(initOrObject)) {
287
- this._state = SpvFromBTCSwapState.CREATED;
288
- this.quoteId = initOrObject.quoteId;
289
- this.recipient = initOrObject.recipient;
290
- this.vaultOwner = initOrObject.vaultOwner;
291
- this.vaultId = initOrObject.vaultId;
292
- this.vaultRequiredConfirmations = initOrObject.vaultRequiredConfirmations;
293
- this.vaultTokenMultipliers = initOrObject.vaultTokenMultipliers;
294
- this.vaultBtcAddress = initOrObject.vaultBtcAddress;
295
- this.vaultUtxo = initOrObject.vaultUtxo;
296
- this.vaultUtxoValue = initOrObject.vaultUtxoValue;
297
- this.btcDestinationAddress = initOrObject.btcDestinationAddress;
298
- this.btcAmount = initOrObject.btcAmount;
299
- this.btcAmountSwap = initOrObject.btcAmountSwap;
300
- this.btcAmountGas = initOrObject.btcAmountGas;
301
- this.minimumBtcFeeRate = initOrObject.minimumBtcFeeRate;
302
- this.outputTotalSwap = initOrObject.outputTotalSwap;
303
- this.outputSwapToken = initOrObject.outputSwapToken;
304
- this.outputTotalGas = initOrObject.outputTotalGas;
305
- this.outputGasToken = initOrObject.outputGasToken;
306
- this.gasSwapFeeBtc = initOrObject.gasSwapFeeBtc;
307
- this.gasSwapFee = initOrObject.gasSwapFee;
308
- this.callerFeeShare = initOrObject.callerFeeShare;
309
- this.frontingFeeShare = initOrObject.frontingFeeShare;
310
- this.executionFeeShare = initOrObject.executionFeeShare;
311
- this._genesisSmartChainBlockHeight = initOrObject.genesisSmartChainBlockHeight;
312
- this.gasPricingInfo = initOrObject.gasPricingInfo;
313
- const vaultAddressType = toCoinselectAddressType(toOutputScript(this.wrapper._options.bitcoinNetwork, this.vaultBtcAddress));
314
- if(vaultAddressType!=="p2tr" && vaultAddressType!=="p2wpkh" && vaultAddressType!=="p2wsh")
315
- throw new Error("Vault address type must be of witness type: p2tr, p2wpkh, p2wsh");
316
- } else {
317
- this.quoteId = initOrObject.quoteId;
318
- this.recipient = initOrObject.recipient;
319
- this.vaultOwner = initOrObject.vaultOwner;
320
- this.vaultId = BigInt(initOrObject.vaultId);
321
- this.vaultRequiredConfirmations = initOrObject.vaultRequiredConfirmations;
322
- this.vaultTokenMultipliers = initOrObject.vaultTokenMultipliers.map((val: string) => BigInt(val));
323
- this.vaultBtcAddress = initOrObject.vaultBtcAddress;
324
- this.vaultUtxo = initOrObject.vaultUtxo;
325
- this.vaultUtxoValue = BigInt(initOrObject.vaultUtxoValue);
326
- this.btcDestinationAddress = initOrObject.btcDestinationAddress;
327
- this.btcAmount = BigInt(initOrObject.btcAmount);
328
- this.btcAmountSwap = BigInt(initOrObject.btcAmountSwap);
329
- this.btcAmountGas = BigInt(initOrObject.btcAmountGas);
330
- this.minimumBtcFeeRate = initOrObject.minimumBtcFeeRate;
331
- this.outputTotalSwap = BigInt(initOrObject.outputTotalSwap);
332
- this.outputSwapToken = initOrObject.outputSwapToken;
333
- this.outputTotalGas = BigInt(initOrObject.outputTotalGas);
334
- this.outputGasToken = initOrObject.outputGasToken;
335
- this.gasSwapFeeBtc = BigInt(initOrObject.gasSwapFeeBtc);
336
- this.gasSwapFee = BigInt(initOrObject.gasSwapFee);
337
- this.callerFeeShare = BigInt(initOrObject.callerFeeShare);
338
- this.frontingFeeShare = BigInt(initOrObject.frontingFeeShare);
339
- this.executionFeeShare = BigInt(initOrObject.executionFeeShare);
340
- this._genesisSmartChainBlockHeight = initOrObject.genesisSmartChainBlockHeight;
341
- this._senderAddress = initOrObject.senderAddress;
342
- this._claimTxId = initOrObject.claimTxId;
343
- this._frontTxId = initOrObject.frontTxId;
344
- this.gasPricingInfo = deserializePriceInfoType(initOrObject.gasPricingInfo);
345
- this.btcTxConfirmedAt = initOrObject.btcTxConfirmedAt;
346
- this.posted = initOrObject.posted;
347
- if(initOrObject.data!=null) this._data = new (this.wrapper._spvWithdrawalDataDeserializer(this._contractVersion))(initOrObject.data);
348
- }
349
- this.tryCalculateSwapFee();
350
- this.logger = getLogger("SPVFromBTC("+this.getId()+"): ");
351
-
352
- this._contract = wrapper._contract(this._contractVersion);
353
- }
354
-
355
- /**
356
- * @inheritDoc
357
- * @internal
358
- */
359
- protected upgradeVersion() {
360
- if(this.version===1) {
361
- this.posted = this.initiated && this._data!=null;
362
- this.version = 2;
363
- }
364
- }
365
-
366
- /**
367
- * @inheritDoc
368
- * @internal
369
- */
370
- protected tryCalculateSwapFee() {
371
- if(this.swapFeeBtc==null && this.swapFee!=null) {
372
- this.swapFeeBtc = this.swapFee * this.btcAmountSwap / this.getOutputWithoutFee().rawAmount;
373
- }
374
-
375
- if(this.pricingInfo!=null && this.pricingInfo.swapPriceUSatPerToken==null) {
376
- const priceUsdPerBtc = this.pricingInfo.realPriceUsdPerBitcoin;
377
- this.pricingInfo = this.wrapper._prices.recomputePriceInfoReceive(
378
- this.chainIdentifier,
379
- this.btcAmountSwap,
380
- this.pricingInfo.satsBaseFee,
381
- this.pricingInfo.feePPM,
382
- this.getOutputWithoutFee().rawAmount,
383
- this.outputSwapToken
384
- );
385
- this.pricingInfo.realPriceUsdPerBitcoin = priceUsdPerBtc;
386
- }
387
- }
388
-
389
-
390
- //////////////////////////////
391
- //// Pricing
392
-
393
- /**
394
- * @inheritDoc
395
- */
396
- async refreshPriceData(): Promise<void> {
397
- if(this.pricingInfo==null) return;
398
- const usdPricePerBtc = this.pricingInfo.realPriceUsdPerBitcoin;
399
- this.pricingInfo = await this.wrapper._prices.isValidAmountReceive(
400
- this.chainIdentifier,
401
- this.btcAmountSwap,
402
- this.pricingInfo.satsBaseFee,
403
- this.pricingInfo.feePPM,
404
- this.getOutputWithoutFee().rawAmount,
405
- this.outputSwapToken,
406
- undefined,
407
- undefined,
408
- this.swapFeeBtc
409
- );
410
- this.pricingInfo.realPriceUsdPerBitcoin = usdPricePerBtc;
411
- }
412
-
413
-
414
- //////////////////////////////
415
- //// Getters & utils
416
-
417
- /**
418
- * @inheritDoc
419
- * @internal
420
- */
421
- _getInitiator(): string {
422
- return this.recipient;
423
- }
424
-
425
- /**
426
- * @inheritDoc
427
- * @internal
428
- */
429
- _getEscrowHash(): string | null {
430
- return this._data?.btcTx?.txid ?? null;
431
- }
432
-
433
- /**
434
- * @inheritDoc
435
- */
436
- getId(): string {
437
- return this.quoteId+this._randomNonce;
438
- }
439
-
440
- /**
441
- * @inheritDoc
442
- */
443
- getQuoteExpiry(): number {
444
- return this.expiry - 20*1000;
445
- }
446
-
447
- /**
448
- * @inheritDoc
449
- * @internal
450
- */
451
- _verifyQuoteDefinitelyExpired(): Promise<boolean> {
452
- return Promise.resolve(this.expiry<Date.now());
453
- }
454
-
455
- /**
456
- * @inheritDoc
457
- * @internal
458
- */
459
- _verifyQuoteValid(): Promise<boolean> {
460
- return Promise.resolve(this.expiry>Date.now() && (this._state===SpvFromBTCSwapState.CREATED || this._state===SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED));
461
- }
462
-
463
- /**
464
- * @inheritDoc
465
- */
466
- getOutputAddress(): string | null {
467
- return this.recipient;
468
- }
469
-
470
- /**
471
- * @inheritDoc
472
- */
473
- getOutputTxId(): string | null {
474
- return this._frontTxId ?? this._claimTxId ?? null;
475
- }
476
-
477
- /**
478
- * @inheritDoc
479
- */
480
- getInputAddress(): string | null {
481
- return this._senderAddress ?? null;
482
- }
483
-
484
- /**
485
- * @inheritDoc
486
- */
487
- getInputTxId(): string | null {
488
- return this._data?.btcTx?.txid ?? null;
489
- }
490
-
491
- /**
492
- * @inheritDoc
493
- */
494
- requiresAction(): boolean {
495
- return this._state===SpvFromBTCSwapState.BTC_TX_CONFIRMED;
496
- }
497
-
498
- /**
499
- * @inheritDoc
500
- */
501
- isFinished(): boolean {
502
- return this._state===SpvFromBTCSwapState.CLAIMED || this._state===SpvFromBTCSwapState.QUOTE_EXPIRED || this._state===SpvFromBTCSwapState.FAILED;
503
- }
504
-
505
- /**
506
- * @inheritDoc
507
- */
508
- isClaimable(): boolean {
509
- return this._state===SpvFromBTCSwapState.BTC_TX_CONFIRMED;
510
- }
511
-
512
- /**
513
- * @inheritDoc
514
- */
515
- isSuccessful(): boolean {
516
- return this._state===SpvFromBTCSwapState.FRONTED || this._state===SpvFromBTCSwapState.CLAIMED;
517
- }
518
-
519
- /**
520
- * @inheritDoc
521
- */
522
- isFailed(): boolean {
523
- return this._state===SpvFromBTCSwapState.FAILED || this._state===SpvFromBTCSwapState.DECLINED || this._state===SpvFromBTCSwapState.CLOSED;
524
- }
525
-
526
- /**
527
- * @inheritDoc
528
- */
529
- isInProgress(): boolean {
530
- return this._state===SpvFromBTCSwapState.POSTED ||
531
- this._state===SpvFromBTCSwapState.BROADCASTED ||
532
- this._state===SpvFromBTCSwapState.BTC_TX_CONFIRMED;
533
- }
534
-
535
- /**
536
- * @inheritDoc
537
- */
538
- isQuoteExpired(): boolean {
539
- return this._state===SpvFromBTCSwapState.QUOTE_EXPIRED;
540
- }
541
-
542
- /**
543
- * @inheritDoc
544
- */
545
- isQuoteSoftExpired(): boolean {
546
- return this._state===SpvFromBTCSwapState.QUOTE_EXPIRED || this._state===SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED;
547
- }
548
-
549
- /**
550
- * Returns the data about used spv vault (UTXO-controlled vault) to perform the swap
551
- */
552
- getSpvVaultData(): {
553
- owner: string,
554
- vaultId: bigint,
555
- utxo: string
556
- } {
557
- return {
558
- owner: this.vaultOwner,
559
- vaultId: this.vaultId,
560
- utxo: this.vaultUtxo
561
- }
562
- }
563
-
564
-
565
- //////////////////////////////
566
- //// Amounts & fees
567
-
568
- /**
569
- * Returns the input BTC amount in sats without any fees
570
- *
571
- * @internal
572
- */
573
- protected getInputSwapAmountWithoutFee(): bigint {
574
- return (this.btcAmountSwap - this.swapFeeBtc) * 100_000n / (100_000n + this.callerFeeShare + this.frontingFeeShare + this.executionFeeShare);
575
- }
576
-
577
- /**
578
- * Returns the input gas BTC amount in sats without any fees
579
- *
580
- * @internal
581
- */
582
- protected getInputGasAmountWithoutFee(): bigint {
583
- return (this.btcAmountGas - this.gasSwapFeeBtc) * 100_000n / (100_000n + this.callerFeeShare + this.frontingFeeShare);
584
- }
585
-
586
- /**
587
- * Returns to total input BTC amount in sats without any fees (this is BTC amount for the swap + BTC amount
588
- * for the gas drop).
589
- *
590
- * @internal
591
- */
592
- protected getInputAmountWithoutFee(): bigint {
593
- return this.getInputSwapAmountWithoutFee() + this.getInputGasAmountWithoutFee();
594
- }
595
-
596
- /**
597
- * Returns the swap output amount without any fees, this value is therefore always higher than
598
- * the actual received output.
599
- *
600
- * @internal
601
- */
602
- protected getOutputWithoutFee(): TokenAmount<SCToken<T["ChainId"]>, true> {
603
- return toTokenAmount(
604
- (this.outputTotalSwap * (100_000n + this.callerFeeShare + this.frontingFeeShare + this.executionFeeShare) / 100_000n) + (this.swapFee ?? 0n),
605
- this.wrapper._tokens[this.outputSwapToken], this.wrapper._prices, this.pricingInfo
606
- );
607
- }
608
-
609
- /**
610
- * Returns the swap fee charged by the intermediary (LP) on this swap
611
- *
612
- * @internal
613
- */
614
- protected getSwapFee(): Fee<T["ChainId"], BtcToken<false>, SCToken<T["ChainId"]>> {
615
- if(this.pricingInfo==null) throw new Error("No pricing info known, cannot estimate fee!");
616
-
617
- const outputToken = this.wrapper._tokens[this.outputSwapToken];
618
- const gasSwapFeeInOutputToken = this.gasSwapFeeBtc
619
- * (10n ** BigInt(outputToken.decimals))
620
- * 1_000_000n
621
- / this.pricingInfo.swapPriceUSatPerToken;
622
-
623
- const feeWithoutBaseFee = this.swapFeeBtc - this.pricingInfo.satsBaseFee;
624
- const swapFeePPM = feeWithoutBaseFee * 1000000n / (this.btcAmount - this.swapFeeBtc - this.gasSwapFeeBtc);
625
-
626
- const amountInSrcToken = toTokenAmount(
627
- this.swapFeeBtc + this.gasSwapFeeBtc, BitcoinTokens.BTC, this.wrapper._prices, this.pricingInfo
628
- );
629
- return {
630
- amountInSrcToken,
631
- amountInDstToken: toTokenAmount(this.swapFee + gasSwapFeeInOutputToken, outputToken, this.wrapper._prices, this.pricingInfo),
632
- currentUsdValue: amountInSrcToken.currentUsdValue,
633
- usdValue: amountInSrcToken.usdValue,
634
- pastUsdValue: amountInSrcToken.pastUsdValue,
635
- composition: {
636
- base: toTokenAmount(this.pricingInfo.satsBaseFee, BitcoinTokens.BTC, this.wrapper._prices, this.pricingInfo),
637
- percentage: ppmToPercentage(swapFeePPM)
638
- }
639
- };
640
- }
641
-
642
- /**
643
- * Returns the fee to be paid to watchtowers on the destination chain to automatically
644
- * process and settle this swap without requiring any user interaction
645
- *
646
- * @internal
647
- */
648
- protected getWatchtowerFee(): Fee<T["ChainId"], BtcToken<false>, SCToken<T["ChainId"]>> {
649
- if(this.pricingInfo==null) throw new Error("No pricing info known, cannot estimate fee!");
650
-
651
- const totalFeeShare = this.callerFeeShare + this.frontingFeeShare;
652
- const outputToken = this.wrapper._tokens[this.outputSwapToken];
653
- const watchtowerFeeInOutputToken = this.getInputGasAmountWithoutFee() * totalFeeShare
654
- * (10n ** BigInt(outputToken.decimals))
655
- * 1_000_000n
656
- / this.pricingInfo.swapPriceUSatPerToken
657
- / 100_000n;
658
- const feeBtc = this.getInputAmountWithoutFee() * (totalFeeShare + this.executionFeeShare) / 100_000n;
659
- const amountInSrcToken = toTokenAmount(feeBtc, BitcoinTokens.BTC, this.wrapper._prices, this.pricingInfo);
660
- return {
661
- amountInSrcToken,
662
- amountInDstToken: toTokenAmount(
663
- (this.outputTotalSwap * (totalFeeShare + this.executionFeeShare) / 100_000n) + watchtowerFeeInOutputToken,
664
- outputToken, this.wrapper._prices, this.pricingInfo
665
- ),
666
- currentUsdValue: amountInSrcToken.currentUsdValue,
667
- usdValue: amountInSrcToken.usdValue,
668
- pastUsdValue: amountInSrcToken.pastUsdValue
669
- };
670
- }
671
-
672
- /**
673
- * @inheritDoc
674
- */
675
- getFee(): Fee<T["ChainId"], BtcToken<false>, SCToken<T["ChainId"]>> {
676
- const swapFee = this.getSwapFee();
677
- const watchtowerFee = this.getWatchtowerFee();
678
-
679
- const amountInSrcToken = toTokenAmount(
680
- swapFee.amountInSrcToken.rawAmount + watchtowerFee.amountInSrcToken.rawAmount,
681
- BitcoinTokens.BTC, this.wrapper._prices, this.pricingInfo
682
- );
683
- return {
684
- amountInSrcToken,
685
- amountInDstToken: toTokenAmount(
686
- swapFee.amountInDstToken.rawAmount + watchtowerFee.amountInDstToken.rawAmount,
687
- this.wrapper._tokens[this.outputSwapToken], this.wrapper._prices, this.pricingInfo
688
- ),
689
- currentUsdValue: amountInSrcToken.currentUsdValue,
690
- usdValue: amountInSrcToken.usdValue,
691
- pastUsdValue: amountInSrcToken.pastUsdValue
692
- };
693
- }
694
-
695
- /**
696
- * @inheritDoc
697
- */
698
- getFeeBreakdown(): [
699
- {type: FeeType.SWAP, fee: Fee<T["ChainId"], BtcToken<false>, SCToken<T["ChainId"]>>},
700
- {type: FeeType.NETWORK_OUTPUT, fee: Fee<T["ChainId"], BtcToken<false>, SCToken<T["ChainId"]>>}
701
- ] {
702
- return [
703
- {
704
- type: FeeType.SWAP,
705
- fee: this.getSwapFee()
706
- },
707
- {
708
- type: FeeType.NETWORK_OUTPUT,
709
- fee: this.getWatchtowerFee()
710
- }
711
- ];
712
- }
713
-
714
- /**
715
- * @inheritDoc
716
- */
717
- getOutputToken(): SCToken<T["ChainId"]> {
718
- return this.wrapper._tokens[this.outputSwapToken];
719
- }
720
-
721
- /**
722
- * @inheritDoc
723
- */
724
- getOutput(): TokenAmount<SCToken<T["ChainId"]>, true> {
725
- return toTokenAmount(this.outputTotalSwap, this.wrapper._tokens[this.outputSwapToken], this.wrapper._prices, this.pricingInfo);
726
- }
727
-
728
- /**
729
- * @inheritDoc
730
- */
731
- getGasDropOutput(): TokenAmount<SCToken<T["ChainId"]>, true> {
732
- return toTokenAmount(this.outputTotalGas, this.wrapper._tokens[this.outputGasToken], this.wrapper._prices, this.gasPricingInfo);
733
- }
734
-
735
- /**
736
- * @inheritDoc
737
- */
738
- getInputWithoutFee(): TokenAmount<BtcToken<false>, true> {
739
- return toTokenAmount(this.getInputAmountWithoutFee(), BitcoinTokens.BTC, this.wrapper._prices, this.pricingInfo);
740
- }
741
-
742
- /**
743
- * @inheritDoc
744
- */
745
- getInputToken(): BtcToken<false> {
746
- return BitcoinTokens.BTC;
747
- }
748
-
749
- /**
750
- * @inheritDoc
751
- */
752
- getInput(): TokenAmount<BtcToken<false>, true> {
753
- return toTokenAmount(this.btcAmount, BitcoinTokens.BTC, this.wrapper._prices, this.pricingInfo);
754
- }
755
-
756
-
757
- //////////////////////////////
758
- //// Bitcoin tx
759
-
760
- /**
761
- * @inheritDoc
762
- */
763
- getRequiredConfirmationsCount(): number {
764
- return this.vaultRequiredConfirmations;
765
- }
766
-
767
- /**
768
- * Returns raw transaction details that can be used to manually create a swap PSBT. It is better to use
769
- * the {@link getPsbt} or {@link getFundedPsbt} function retrieve an already prepared PSBT.
770
- */
771
- async getTransactionDetails(): Promise<{
772
- in0txid: string,
773
- in0vout: number,
774
- in0sequence: number,
775
- vaultAmount: bigint,
776
- vaultScript: Uint8Array,
777
- in1sequence: number,
778
- out1script: Uint8Array,
779
- out2amount: bigint,
780
- out2script: Uint8Array,
781
- locktime: number
782
- }> {
783
- const [txId, voutStr] = this.vaultUtxo.split(":");
784
-
785
- const vaultScript = toOutputScript(this.wrapper._options.bitcoinNetwork, this.vaultBtcAddress);
786
-
787
- const out2script = toOutputScript(this.wrapper._options.bitcoinNetwork, this.btcDestinationAddress);
788
-
789
- const opReturnData = this._contract.toOpReturnData(
790
- this.recipient,
791
- [
792
- this.outputTotalSwap / this.vaultTokenMultipliers[0],
793
- this.outputTotalGas / this.vaultTokenMultipliers[1]
794
- ]
795
- );
796
- const out1script = Buffer.concat([
797
- opReturnData.length > 75 ? Buffer.from([0x6a, 0x4c, opReturnData.length]) : Buffer.from([0x6a, opReturnData.length]),
798
- opReturnData
799
- ]);
800
-
801
- if(this.callerFeeShare<0n || this.callerFeeShare>0xFFFFFn) throw new Error("Caller fee out of bounds!");
802
- if(this.frontingFeeShare<0n || this.frontingFeeShare>0xFFFFFn) throw new Error("Fronting fee out of bounds!");
803
- if(this.executionFeeShare<0n || this.executionFeeShare>0xFFFFFn) throw new Error("Execution fee out of bounds!");
804
-
805
- const nSequence0 = 0x80000000n | (this.callerFeeShare & 0xFFFFFn) | (this.frontingFeeShare & 0b1111_1111_1100_0000_0000n) << 10n;
806
- const nSequence1 = 0x80000000n | (this.executionFeeShare & 0xFFFFFn) | (this.frontingFeeShare & 0b0000_0000_0011_1111_1111n) << 20n;
807
-
808
- return {
809
- in0txid: txId,
810
- in0vout: parseInt(voutStr),
811
- in0sequence: Number(nSequence0),
812
- vaultAmount: this.vaultUtxoValue,
813
- vaultScript,
814
- in1sequence: Number(nSequence1),
815
- out1script,
816
- out2amount: this.btcAmount,
817
- out2script,
818
- locktime: 500_000_000 + Math.floor(Math.random() * 1_000_000_000) //Use this as a random salt to make the btc txId unique!
819
- };
820
- }
821
-
822
- /**
823
- * Returns the raw PSBT (not funded), the wallet should fund the PSBT (add its inputs) and importantly **set the nSequence field of the
824
- * 2nd input** (input 1 - indexing from 0) to the value returned in `in1sequence`, sign the PSBT and then pass
825
- * it back to the swap with {@link submitPsbt} function.
826
- */
827
- async getPsbt(): Promise<{
828
- psbt: Transaction,
829
- psbtHex: string,
830
- psbtBase64: string,
831
- in1sequence: number
832
- }> {
833
- const res = await this.getTransactionDetails();
834
- const psbt = new Transaction({
835
- allowUnknownOutputs: true,
836
- allowLegacyWitnessUtxo: true,
837
- lockTime: res.locktime
838
- });
839
- psbt.addInput({
840
- txid: res.in0txid,
841
- index: res.in0vout,
842
- witnessUtxo: {
843
- amount: res.vaultAmount,
844
- script: res.vaultScript
845
- },
846
- sequence: res.in0sequence
847
- });
848
- psbt.addOutput({
849
- amount: res.vaultAmount,
850
- script: res.vaultScript
851
- });
852
- psbt.addOutput({
853
- amount: 0n,
854
- script: res.out1script
855
- });
856
- psbt.addOutput({
857
- amount: res.out2amount,
858
- script: res.out2script
859
- });
860
- const serializedPsbt = Buffer.from(psbt.toPSBT());
861
- return {
862
- psbt,
863
- psbtHex: serializedPsbt.toString("hex"),
864
- psbtBase64: serializedPsbt.toString("base64"),
865
- in1sequence: res.in1sequence
866
- };
867
- }
868
-
869
- /**
870
- * Returns the PSBT that is already funded with wallet's UTXOs (runs a coin-selection algorithm to choose UTXOs to use),
871
- * also returns inputs indices that need to be signed by the wallet before submitting the PSBT back to the SDK with
872
- * {@link submitPsbt}
873
- *
874
- * @remarks
875
- * Note that when passing the `feeRate` argument, the fee must be at least {@link minimumBtcFeeRate} sats/vB.
876
- *
877
- * @param _bitcoinWallet Sender's bitcoin wallet
878
- * @param feeRate Optional fee rate in sats/vB for the transaction
879
- * @param additionalOutputs additional outputs to add to the PSBT - can be used to collect fees from users
880
- */
881
- async getFundedPsbt(
882
- _bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface,
883
- feeRate?: number,
884
- additionalOutputs?: ({amount: bigint, outputScript: Uint8Array} | {amount: bigint, address: string})[]
885
- ): Promise<{
886
- psbt: Transaction,
887
- psbtHex: string,
888
- psbtBase64: string,
889
- signInputs: number[]
890
- }> {
891
- const bitcoinWallet: IBitcoinWallet = toBitcoinWallet(_bitcoinWallet, this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork);
892
- if(feeRate!=null) {
893
- if(feeRate<this.minimumBtcFeeRate) throw new Error("Bitcoin tx fee needs to be at least "+this.minimumBtcFeeRate+" sats/vB");
894
- } else {
895
- feeRate = Math.max(this.minimumBtcFeeRate, await bitcoinWallet.getFeeRate());
896
- }
897
- let {psbt, in1sequence} = await this.getPsbt();
898
- if(additionalOutputs!=null) additionalOutputs.forEach(output => {
899
- psbt.addOutput({
900
- amount: output.amount,
901
- script: (output as {outputScript: Uint8Array}).outputScript ?? toOutputScript(this.wrapper._options.bitcoinNetwork, (output as {address: string}).address)
902
- });
903
- });
904
- psbt = await bitcoinWallet.fundPsbt(psbt, feeRate);
905
- psbt.updateInput(1, {sequence: in1sequence});
906
- //Sign every input except the first one
907
- const signInputs: number[] = [];
908
- for(let i=1;i<psbt.inputsLength;i++) {
909
- signInputs.push(i);
910
- }
911
- const serializedPsbt = Buffer.from(psbt.toPSBT());
912
- return {
913
- psbt,
914
- psbtHex: serializedPsbt.toString("hex"),
915
- psbtBase64: serializedPsbt.toString("base64"),
916
- signInputs
917
- };
918
- }
919
-
920
- /**
921
- * @inheritDoc
922
- */
923
- async submitPsbt(_psbt: Transaction | string): Promise<string> {
924
- const psbt = parsePsbtTransaction(_psbt);
925
-
926
- //Ensure not expired
927
- if(this.expiry<Date.now()) {
928
- throw new Error("Quote expired!");
929
- }
930
-
931
- //Ensure valid state
932
- if(this._state!==SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED && this._state!==SpvFromBTCSwapState.CREATED) {
933
- throw new Error("Invalid swap state!");
934
- }
935
- if(this.url==null) throw new Error("LP URL not known, cannot submit PSBT!");
936
-
937
- //Ensure all inputs except the 1st are finalized
938
- for(let i=1;i<psbt.inputsLength;i++) {
939
- if(getInputType(psbt.getInput(i)).txType==="legacy")
940
- throw new Error("Legacy (non-segwit) inputs are not allowed in the transaction!");
941
- psbt.finalizeIdx(i);
942
- }
943
- const btcTx = await this.wrapper._btcRpc.parseTransaction(Buffer.from(psbt.toBytes(true)).toString("hex"));
944
- const data = await this._contract.getWithdrawalData(btcTx);
945
-
946
- this.logger.debug("submitPsbt(): parsed withdrawal data: ", data);
947
-
948
- //Verify correct withdrawal data
949
- if(
950
- !data.isRecipient(this.recipient) ||
951
- data.rawAmounts[0]*this.vaultTokenMultipliers[0] !== this.outputTotalSwap ||
952
- (data.rawAmounts[1] ?? 0n)*this.vaultTokenMultipliers[1] !== this.outputTotalGas ||
953
- data.callerFeeRate!==this.callerFeeShare ||
954
- data.frontingFeeRate!==this.frontingFeeShare ||
955
- data.executionFeeRate!==this.executionFeeShare ||
956
- data.getSpentVaultUtxo()!==this.vaultUtxo ||
957
- BigInt(data.getNewVaultBtcAmount())!==this.vaultUtxoValue ||
958
- !data.getNewVaultScript().equals(toOutputScript(this.wrapper._options.bitcoinNetwork, this.vaultBtcAddress)) ||
959
- data.getExecutionData()!=null
960
- ) {
961
- throw new Error("Invalid withdrawal tx data submitted!");
962
- }
963
-
964
- //Verify correct LP output
965
- const lpOutput = psbt.getOutput(2);
966
- if(
967
- lpOutput.script==null ||
968
- lpOutput.amount!==this.btcAmount ||
969
- !toOutputScript(this.wrapper._options.bitcoinNetwork, this.btcDestinationAddress).equals(Buffer.from(lpOutput.script))
970
- ) {
971
- throw new Error("Invalid LP bitcoin output in transaction!");
972
- }
973
-
974
- //Verify vault utxo not spent yet
975
- if(await this.wrapper._btcRpc.isSpent(this.vaultUtxo)) {
976
- throw new Error("Vault UTXO already spent, please create new swap!");
977
- }
978
-
979
- //Verify tx is parsable by the contract
980
- try {
981
- await this._contract.checkWithdrawalTx(data);
982
- } catch (e: any) {
983
- throw new Error("Transaction not parsable by the contract: "+(e.message ?? e.toString()));
984
- }
985
-
986
- //Ensure still not expired
987
- if(this.expiry<Date.now()) {
988
- throw new Error("Quote expired!");
989
- }
990
-
991
- this._data = data;
992
- this.initiated = true;
993
- this.posted = true;
994
- await this._saveAndEmit(SpvFromBTCSwapState.SIGNED);
995
-
996
- try {
997
- await IntermediaryAPI.initSpvFromBTC(
998
- this.chainIdentifier,
999
- this.url,
1000
- {
1001
- quoteId: this.quoteId,
1002
- psbtHex: Buffer.from(psbt.toPSBT(0)).toString("hex")
1003
- }
1004
- );
1005
- await this._saveAndEmit(SpvFromBTCSwapState.POSTED);
1006
- } catch (e) {
1007
- await this._saveAndEmit(SpvFromBTCSwapState.DECLINED);
1008
- throw e;
1009
- }
1010
-
1011
- return this._data.getTxId();
1012
- }
1013
-
1014
- /**
1015
- * @inheritDoc
1016
- */
1017
- async estimateBitcoinFee(_bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface, feeRate?: number): Promise<TokenAmount<BtcToken<false>, true> | null> {
1018
- const bitcoinWallet: IBitcoinWallet = toBitcoinWallet(_bitcoinWallet, this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork);
1019
- const txFee = await bitcoinWallet.getFundedPsbtFee((await this.getPsbt()).psbt, feeRate);
1020
- if(txFee==null) return null;
1021
- return toTokenAmount(BigInt(txFee), BitcoinTokens.BTC, this.wrapper._prices, this.pricingInfo);
1022
- }
1023
-
1024
- /**
1025
- * @inheritDoc
1026
- */
1027
- async sendBitcoinTransaction(wallet: IBitcoinWallet | MinimalBitcoinWalletInterfaceWithSigner, feeRate?: number): Promise<string> {
1028
- const {psbt, psbtBase64, psbtHex, signInputs} = await this.getFundedPsbt(wallet, feeRate);
1029
- let signedPsbt: Transaction | string;
1030
- if(isIBitcoinWallet(wallet)) {
1031
- signedPsbt = await wallet.signPsbt(psbt, signInputs);
1032
- } else {
1033
- signedPsbt = await wallet.signPsbt({
1034
- psbt, psbtHex, psbtBase64
1035
- }, signInputs);
1036
- }
1037
- return await this.submitPsbt(signedPsbt);
1038
- }
1039
-
1040
- /**
1041
- * Executes the swap with the provided bitcoin wallet
1042
- *
1043
- * @param wallet Bitcoin wallet to use to sign the bitcoin transaction
1044
- * @param callbacks Callbacks to track the progress of the swap
1045
- * @param options Optional options for the swap like feeRate, AbortSignal, and timeouts/intervals
1046
- *
1047
- * @returns {boolean} Whether a swap was settled automatically by swap watchtowers or requires manual claim by the
1048
- * user, in case `false` is returned the user should call the {@link claim} function to settle the swap on the
1049
- * destination manually
1050
- */
1051
- async execute(
1052
- wallet: IBitcoinWallet | MinimalBitcoinWalletInterfaceWithSigner,
1053
- callbacks?: {
1054
- onSourceTransactionSent?: (sourceTxId: string) => void,
1055
- onSourceTransactionConfirmationStatus?: (sourceTxId?: string, confirmations?: number, targetConfirations?: number, etaMs?: number) => void,
1056
- onSourceTransactionConfirmed?: (sourceTxId: string) => void,
1057
- onSwapSettled?: (destinationTxId: string) => void
1058
- },
1059
- options?: {
1060
- feeRate?: number,
1061
- abortSignal?: AbortSignal,
1062
- btcTxCheckIntervalSeconds?: number,
1063
- maxWaitTillAutomaticSettlementSeconds?: number
1064
- }
1065
- ): Promise<boolean> {
1066
- if(this._state===SpvFromBTCSwapState.CLOSED) throw new Error("Swap encountered a catastrophic failure!");
1067
- if(this._state===SpvFromBTCSwapState.FAILED) throw new Error("Swap failed!");
1068
- if(this._state===SpvFromBTCSwapState.DECLINED) throw new Error("Swap execution already declined by the LP!");
1069
- if(this._state===SpvFromBTCSwapState.QUOTE_EXPIRED) throw new Error("Swap quote expired!");
1070
- if(this._state===SpvFromBTCSwapState.CLAIMED || this._state===SpvFromBTCSwapState.FRONTED) throw new Error("Swap already settled or fronted!");
1071
-
1072
- if(this._state===SpvFromBTCSwapState.CREATED) {
1073
- const txId = await this.sendBitcoinTransaction(wallet, options?.feeRate);
1074
- if(callbacks?.onSourceTransactionSent!=null) callbacks.onSourceTransactionSent(txId);
1075
- }
1076
- if(this._state===SpvFromBTCSwapState.POSTED || this._state===SpvFromBTCSwapState.BROADCASTED) {
1077
- const txId = await this.waitForBitcoinTransaction(callbacks?.onSourceTransactionConfirmationStatus, options?.btcTxCheckIntervalSeconds, options?.abortSignal);
1078
- if (callbacks?.onSourceTransactionConfirmed != null) callbacks.onSourceTransactionConfirmed(txId);
1079
- }
1080
- // @ts-ignore
1081
- if(this._state===SpvFromBTCSwapState.CLAIMED || this._state===SpvFromBTCSwapState.FRONTED) return true;
1082
- if(this._state===SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
1083
- const success = await this.waitTillClaimedOrFronted(options?.maxWaitTillAutomaticSettlementSeconds ?? 60, options?.abortSignal);
1084
- if(success && callbacks?.onSwapSettled!=null) callbacks.onSwapSettled(this.getOutputTxId()!);
1085
- return success;
1086
- }
1087
-
1088
- throw new Error("Unexpected state reached!");
1089
- }
1090
-
1091
- /**
1092
- * @inheritDoc
1093
- *
1094
- * @param options.bitcoinFeeRate Optional fee rate to use for the created Bitcoin transaction
1095
- * @param options.bitcoinWallet Optional bitcoin wallet address specification to return a funded PSBT,
1096
- * if not provided a raw PSBT is returned instead which necessitates the implementor to manually add
1097
- * inputs to the bitcoin transaction and **set the nSequence field of the 2nd input** (input 1 -
1098
- * indexing from 0) to the value returned in `in1sequence`
1099
- */
1100
- async txsExecute(options?: {
1101
- bitcoinFeeRate?: number,
1102
- bitcoinWallet?: MinimalBitcoinWalletInterface,
1103
- }): Promise<[
1104
- SwapExecutionActionBitcoin<"RAW_PSBT" | "FUNDED_PSBT">
1105
- ]> {
1106
- if(this._state===SpvFromBTCSwapState.CREATED) {
1107
- if (!await this._verifyQuoteValid()) throw new Error("Quote already expired or close to expiry!");
1108
- return [
1109
- {
1110
- name: "Payment",
1111
- description: "Send funds to the bitcoin swap address",
1112
- chain: "BITCOIN",
1113
- txs: [
1114
- options?.bitcoinWallet==null
1115
- ? {...await this.getPsbt(), type: "RAW_PSBT"}
1116
- : {...await this.getFundedPsbt(options.bitcoinWallet, options?.bitcoinFeeRate), type: "FUNDED_PSBT"}
1117
- ]
1118
- }
1119
- ];
1120
- }
1121
-
1122
- throw new Error("Invalid swap state to obtain execution txns, required CREATED");
1123
- }
1124
-
1125
-
1126
- /**
1127
- * @inheritDoc
1128
- *
1129
- * @param options.bitcoinFeeRate Optional fee rate to use for the created Bitcoin transaction
1130
- * @param options.bitcoinWallet Optional bitcoin wallet address specification to return a funded PSBT,
1131
- * if not provided a raw PSBT is returned instead which necessitates the implementor to manually add
1132
- * inputs to the bitcoin transaction and **set the nSequence field of the 2nd input** (input 1 -
1133
- * indexing from 0) to the value returned in `in1sequence`
1134
- * @param options.manualSettlementSmartChainSigner Optional smart chain signer to create a manual claim (settlement) transaction
1135
- * @param options.maxWaitTillAutomaticSettlementSeconds Maximum time to wait for an automatic settlement after
1136
- * the bitcoin transaction is confirmed (defaults to 60 seconds)
1137
- */
1138
- async getCurrentActions(options?: {
1139
- bitcoinFeeRate?: number,
1140
- bitcoinWallet?: MinimalBitcoinWalletInterface,
1141
- manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
1142
- maxWaitTillAutomaticSettlementSeconds?: number
1143
- }): Promise<SwapExecutionAction<T>[]> {
1144
- if(this._state===SpvFromBTCSwapState.CREATED) {
1145
- try {
1146
- return await this.txsExecute(options);
1147
- } catch (e) {}
1148
- }
1149
- if(this._state===SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
1150
- if(
1151
- this.btcTxConfirmedAt==null ||
1152
- options?.maxWaitTillAutomaticSettlementSeconds===0 ||
1153
- (Date.now() - this.btcTxConfirmedAt) > (options?.maxWaitTillAutomaticSettlementSeconds ?? 60)*1000
1154
- ) {
1155
- return [{
1156
- name: "Claim" as const,
1157
- description: "Manually settle (claim) the swap on the destination smart chain",
1158
- chain: this.chainIdentifier,
1159
- txs: await this.txsClaim(options?.manualSettlementSmartChainSigner)
1160
- }];
1161
- }
1162
- }
1163
- return [];
1164
- }
1165
-
1166
-
1167
- //////////////////////////////
1168
- //// Bitcoin tx listener
1169
-
1170
- /**
1171
- * Checks whether a bitcoin payment was already made, returns the payment or null when no payment has been made.
1172
- * @internal
1173
- */
1174
- protected async getBitcoinPayment(): Promise<{
1175
- txId: string,
1176
- confirmations: number,
1177
- targetConfirmations: number,
1178
- inputAddresses?: string[]
1179
- } | null> {
1180
- if(this._data?.btcTx?.txid==null) return null;
1181
-
1182
- const result = await this.wrapper._btcRpc.getTransaction(this._data?.btcTx?.txid);
1183
- if(result==null) return null;
1184
-
1185
- return {
1186
- txId: result.txid,
1187
- confirmations: result.confirmations ?? 0,
1188
- targetConfirmations: this.vaultRequiredConfirmations,
1189
- inputAddresses: result.inputAddresses
1190
- }
1191
- }
1192
-
1193
- /**
1194
- * @inheritDoc
1195
- *
1196
- * @throws {Error} if in invalid state (must be {@link SpvFromBTCSwapState.POSTED} or
1197
- * {@link SpvFromBTCSwapState.BROADCASTED} states)
1198
- */
1199
- async waitForBitcoinTransaction(
1200
- updateCallback?: (txId?: string, confirmations?: number, targetConfirmations?: number, txEtaMs?: number) => void,
1201
- checkIntervalSeconds?: number,
1202
- abortSignal?: AbortSignal
1203
- ): Promise<string> {
1204
- if(
1205
- this._state!==SpvFromBTCSwapState.POSTED &&
1206
- this._state!==SpvFromBTCSwapState.BROADCASTED &&
1207
- !(this._state===SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED && this.posted)
1208
- ) throw new Error("Must be in POSTED or BROADCASTED state!");
1209
- if(this._data==null) throw new Error("Expected swap to have withdrawal data filled!");
1210
-
1211
- const result = await this.wrapper._btcRpc.waitForTransaction(
1212
- this._data.btcTx.txid,
1213
- this.vaultRequiredConfirmations,
1214
- (btcTx?: BtcTxWithBlockheight, txEtaMs?: number) => {
1215
- if(updateCallback!=null) updateCallback(btcTx?.txid, btcTx?.confirmations, this.vaultRequiredConfirmations, txEtaMs);
1216
- if(btcTx==null) return;
1217
- let save = false;
1218
- if(btcTx.inputAddresses!=null && this._senderAddress==null) {
1219
- this._senderAddress = btcTx.inputAddresses[1];
1220
- save = true;
1221
- }
1222
- if(this._state===SpvFromBTCSwapState.POSTED || this._state==SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED) {
1223
- this._state = SpvFromBTCSwapState.BROADCASTED;
1224
- save = true;
1225
- }
1226
- if(save) this._saveAndEmit();
1227
- },
1228
- abortSignal,
1229
- checkIntervalSeconds
1230
- );
1231
-
1232
- if(abortSignal!=null) abortSignal.throwIfAborted();
1233
-
1234
- let save = false;
1235
- if(result.inputAddresses!=null && this._senderAddress==null) {
1236
- this._senderAddress = result.inputAddresses[1];
1237
- save = true;
1238
- }
1239
- if(
1240
- (this._state as SpvFromBTCSwapState)!==SpvFromBTCSwapState.FRONTED &&
1241
- (this._state as SpvFromBTCSwapState)!==SpvFromBTCSwapState.CLAIMED
1242
- ) {
1243
- this.btcTxConfirmedAt ??= Date.now();
1244
- this._state = SpvFromBTCSwapState.BTC_TX_CONFIRMED;
1245
- save = true;
1246
- }
1247
- if(save) await this._saveAndEmit();
1248
-
1249
- return result.txid;
1250
- }
1251
-
1252
-
1253
- //////////////////////////////
1254
- //// Claim
1255
-
1256
- /**
1257
- * Returns transactions for settling (claiming) the swap if the swap requires manual settlement, you can check so
1258
- * with isClaimable. After sending the transaction manually be sure to call the waitTillClaimed function to wait
1259
- * till the claim transaction is observed, processed by the SDK and state of the swap properly updated.
1260
- *
1261
- * @remarks
1262
- * Might also return transactions necessary to sync the bitcoin light client.
1263
- *
1264
- * @param _signer Address of the signer to create the claim transactions for, can also be different to the recipient
1265
- *
1266
- * @throws {Error} If the swap is in invalid state (must be {@link SpvFromBTCSwapState.BTC_TX_CONFIRMED})
1267
- */
1268
- async txsClaim(_signer?: string | T["Signer"] | T["NativeSigner"]): Promise<T["TX"][]> {
1269
- let address: string | undefined = undefined;
1270
- if(_signer!=null) {
1271
- if (typeof (_signer) === "string") {
1272
- address = _signer;
1273
- } else if (isAbstractSigner(_signer)) {
1274
- address = _signer.getAddress();
1275
- } else {
1276
- address = (await this.wrapper._chain.wrapSigner(_signer)).getAddress();
1277
- }
1278
- }
1279
-
1280
- if(!this.isClaimable()) throw new Error("Must be in BTC_TX_CONFIRMED state!");
1281
- if(this._data==null) throw new Error("Expected swap to have withdrawal data filled!");
1282
-
1283
- const vaultData = await this._contract.getVaultData(this.vaultOwner, this.vaultId);
1284
- if(vaultData==null) throw new Error(`Vault data for ${this.vaultOwner}:${this.vaultId.toString(10)} not found (already closed???)!`);
1285
-
1286
- const btcTx = await this.wrapper._btcRpc.getTransaction(this._data.btcTx.txid);
1287
- if(btcTx==null) throw new Error(`Bitcoin transaction ${this._data.btcTx.txid} not found!`);
1288
- const txs = [btcTx];
1289
-
1290
- //Trace back from current tx to the vaultData-specified UTXO
1291
- const vaultUtxo = vaultData.getUtxo();
1292
- while(txs[0].ins[0].txid+":"+txs[0].ins[0].vout!==vaultUtxo) {
1293
- const btcTx = await this.wrapper._btcRpc.getTransaction(txs[0].ins[0].txid);
1294
- if(btcTx==null) throw new Error(`Prior withdrawal bitcoin transaction ${this._data.btcTx.txid} not found!`);
1295
- txs.unshift(btcTx);
1296
- }
1297
-
1298
- //Parse transactions to withdrawal data
1299
- const withdrawalData: T["SpvVaultWithdrawalData"][] = [];
1300
- for(let tx of txs) {
1301
- withdrawalData.push(await this._contract.getWithdrawalData(tx));
1302
- }
1303
-
1304
- return await this._contract.txsClaim(
1305
- address ?? this._getInitiator(), vaultData,
1306
- withdrawalData.map(tx => {return {tx}}),
1307
- this.wrapper._synchronizer(this._contractVersion), true
1308
- );
1309
- }
1310
-
1311
- /**
1312
- * Settles the swap by claiming the funds on the destination chain if the swap requires manual settlement, you can
1313
- * check so with isClaimable.
1314
- *
1315
- * @remarks
1316
- * Might also sync the bitcoin light client during the process.
1317
- *
1318
- * @param _signer Signer to use for signing the settlement transactions, can also be different to the recipient
1319
- * @param abortSignal Abort signal
1320
- * @param onBeforeTxSent Optional callback triggered before the claim transaction is broadcasted
1321
- *
1322
- * @throws {Error} If the swap is in invalid state (must be {@link SpvFromBTCSwapState.BTC_TX_CONFIRMED})
1323
- */
1324
- async claim(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, onBeforeTxSent?: (txId: string) => void): Promise<string> {
1325
- const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
1326
- let txIds: string[];
1327
- try {
1328
- let txCount = 0;
1329
- const txs = await this.txsClaim(signer);
1330
- txIds = await this.wrapper._chain.sendAndConfirm(
1331
- signer, txs, true, abortSignal, undefined, (txId: string) => {
1332
- txCount++;
1333
- if(onBeforeTxSent!=null && txCount===txs.length) onBeforeTxSent(txId);
1334
- return Promise.resolve();
1335
- }
1336
- );
1337
- } catch (e) {
1338
- if(this._data==null) throw e;
1339
-
1340
- this.logger.info("claim(): Failed to claim ourselves, checking swap claim state...");
1341
- if(this._state===SpvFromBTCSwapState.CLAIMED) {
1342
- this.logger.info("claim(): Transaction state is CLAIMED, swap was successfully claimed by the watchtower");
1343
- return this._claimTxId!;
1344
- }
1345
- const withdrawalState = await this._contract.getWithdrawalState(this._data, this._genesisSmartChainBlockHeight);
1346
- if(withdrawalState!=null && withdrawalState.type===SpvWithdrawalStateType.CLAIMED) {
1347
- this.logger.info("claim(): Transaction status is CLAIMED, swap was successfully claimed by the watchtower");
1348
- this._claimTxId = withdrawalState.txId;
1349
- await this._saveAndEmit(SpvFromBTCSwapState.CLAIMED);
1350
- return withdrawalState.txId;
1351
- }
1352
- throw e;
1353
- }
1354
-
1355
- this._claimTxId = txIds[0];
1356
- if(
1357
- this._state===SpvFromBTCSwapState.POSTED || this._state===SpvFromBTCSwapState.BROADCASTED ||
1358
- this._state===SpvFromBTCSwapState.BTC_TX_CONFIRMED || this._state===SpvFromBTCSwapState.FAILED ||
1359
- this._state===SpvFromBTCSwapState.FRONTED
1360
- ) {
1361
- await this._saveAndEmit(SpvFromBTCSwapState.CLAIMED);
1362
- }
1363
- return txIds[0];
1364
- }
1365
-
1366
- /**
1367
- * Periodically checks the chain to see whether the swap was finished (claimed or refunded)
1368
- *
1369
- * @param abortSignal
1370
- * @param interval How often to check (in seconds), default to 5s
1371
- * @internal
1372
- */
1373
- protected async watchdogWaitTillResult(abortSignal?: AbortSignal, interval: number = 5): Promise<
1374
- SpvWithdrawalClaimedState | SpvWithdrawalFrontedState | SpvWithdrawalClosedState
1375
- > {
1376
- if(this._data==null) throw new Error("Cannot await the result before the btc transaction is sent!");
1377
-
1378
- let status: SpvWithdrawalState = {type: SpvWithdrawalStateType.NOT_FOUND};
1379
- while(status.type===SpvWithdrawalStateType.NOT_FOUND) {
1380
- await timeoutPromise(interval*1000, abortSignal);
1381
- try {
1382
- //Be smart about checking withdrawal state
1383
- if(await this._shouldCheckWithdrawalState()) {
1384
- status = await this._contract.getWithdrawalState(
1385
- this._data, this._genesisSmartChainBlockHeight
1386
- ) ?? {type: SpvWithdrawalStateType.NOT_FOUND};
1387
- }
1388
- } catch (e) {
1389
- this.logger.error("watchdogWaitTillResult(): Error when fetching commit status: ", e);
1390
- }
1391
- }
1392
- if(abortSignal!=null) abortSignal.throwIfAborted();
1393
- return status;
1394
- }
1395
-
1396
- /**
1397
- * Waits till the swap is successfully settled (claimed), should be called after sending the claim (settlement)
1398
- * transactions manually to wait till the SDK processes the settlement and updates the swap state accordingly.
1399
- *
1400
- * @remarks
1401
- * This is an alias for the {@link waitTillClaimedOrFronted} function and will also resolve if the swap has
1402
- * been fronted (not necessarily claimed)
1403
- *
1404
- * @param maxWaitTimeSeconds – Maximum time in seconds to wait for the swap to be settled
1405
- * @param abortSignal – AbortSignal
1406
- *
1407
- * @returns Whether the swap was claimed in time or not
1408
- */
1409
- waitTillClaimed(maxWaitTimeSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
1410
- return this.waitTillClaimedOrFronted(maxWaitTimeSeconds, abortSignal);
1411
- }
1412
-
1413
- /**
1414
- * Waits till the swap is successfully fronted or settled on the destination chain
1415
- *
1416
- * @param maxWaitTimeSeconds Maximum time in seconds to wait for the swap to be settled (by default
1417
- * it waits indefinitely)
1418
- * @param abortSignal Abort signal
1419
- *
1420
- * @returns {boolean} whether the swap was claimed or fronted automatically or not, if the swap was not claimed
1421
- * the user can claim manually through the {@link claim} function
1422
- */
1423
- async waitTillClaimedOrFronted(maxWaitTimeSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
1424
- if(this._state===SpvFromBTCSwapState.CLAIMED || this._state===SpvFromBTCSwapState.FRONTED) return Promise.resolve(true);
1425
-
1426
- const abortController = extendAbortController(abortSignal);
1427
-
1428
- let timedOut: boolean = false;
1429
- if(maxWaitTimeSeconds!=null) {
1430
- const timeout = setTimeout(() => {
1431
- timedOut = true;
1432
- abortController.abort();
1433
- }, maxWaitTimeSeconds * 1000);
1434
- abortController.signal.addEventListener("abort", () => clearTimeout(timeout));
1435
- }
1436
-
1437
- let res: number | SpvWithdrawalState;
1438
- try {
1439
- res = await Promise.race([
1440
- this.watchdogWaitTillResult(abortController.signal),
1441
- this.waitTillState(SpvFromBTCSwapState.CLAIMED, "eq", abortController.signal).then(() => 0),
1442
- this.waitTillState(SpvFromBTCSwapState.FRONTED, "eq", abortController.signal).then(() => 1),
1443
- this.waitTillState(SpvFromBTCSwapState.FAILED, "eq", abortController.signal).then(() => 2),
1444
- ]);
1445
- abortController.abort();
1446
- } catch (e) {
1447
- abortController.abort();
1448
- if(timedOut) return false;
1449
- throw e;
1450
- }
1451
-
1452
- if(typeof(res)==="number") {
1453
- if(res===0) {
1454
- this.logger.debug("waitTillClaimedOrFronted(): Resolved from state change (CLAIMED)");
1455
- return true;
1456
- }
1457
- if(res===1) {
1458
- this.logger.debug("waitTillClaimedOrFronted(): Resolved from state change (FRONTED)");
1459
- return true;
1460
- }
1461
- if(res===2) {
1462
- this.logger.debug("waitTillClaimedOrFronted(): Resolved from state change (FAILED)");
1463
- throw new Error("Swap failed while waiting for claim or front");
1464
- }
1465
- throw new Error("Invalid numeric response, this should never happen!");
1466
- }
1467
- this.logger.debug("waitTillClaimedOrFronted(): Resolved from watchdog");
1468
-
1469
- if(res.type===SpvWithdrawalStateType.FRONTED) {
1470
- if(
1471
- (this._state as SpvFromBTCSwapState)!==SpvFromBTCSwapState.FRONTED ||
1472
- (this._state as SpvFromBTCSwapState)!==SpvFromBTCSwapState.CLAIMED
1473
- ) {
1474
- this._frontTxId = res.txId;
1475
- await this._saveAndEmit(SpvFromBTCSwapState.FRONTED);
1476
- }
1477
- }
1478
- if(res.type===SpvWithdrawalStateType.CLAIMED) {
1479
- if(
1480
- (this._state as SpvFromBTCSwapState)!==SpvFromBTCSwapState.CLAIMED
1481
- ) {
1482
- this._claimTxId = res.txId;
1483
- await this._saveAndEmit(SpvFromBTCSwapState.CLAIMED);
1484
- }
1485
- }
1486
- if(res.type===SpvWithdrawalStateType.CLOSED) {
1487
- if(
1488
- (this._state as SpvFromBTCSwapState)!==SpvFromBTCSwapState.CLOSED
1489
- ) await this._saveAndEmit(SpvFromBTCSwapState.CLOSED);
1490
- throw new Error("Swap failed with catastrophic error!");
1491
- }
1492
-
1493
- return true;
1494
- }
1495
-
1496
- /**
1497
- * Waits till the bitcoin transaction confirms and swap settled on the destination chain
1498
- *
1499
- * @param updateCallback Callback called when txId is found, and also called with subsequent confirmations
1500
- * @param checkIntervalSeconds How often to check the bitcoin transaction (5 seconds by default)
1501
- * @param abortSignal Abort signal
1502
- *
1503
- * @throws {Error} if in invalid state (must be {@link SpvFromBTCSwapState.POSTED} or
1504
- * {@link SpvFromBTCSwapState.BROADCASTED} states)
1505
- */
1506
- async waitTillExecuted(
1507
- updateCallback?: (txId?: string, confirmations?: number, targetConfirmations?: number, txEtaMs?: number) => void,
1508
- checkIntervalSeconds?: number,
1509
- abortSignal?: AbortSignal
1510
- ): Promise<void> {
1511
- await this.waitForBitcoinTransaction(updateCallback, checkIntervalSeconds, abortSignal);
1512
- await this.waitTillClaimedOrFronted(undefined, abortSignal);
1513
- }
1514
-
1515
-
1516
- //////////////////////////////
1517
- //// Storage
1518
-
1519
- /**
1520
- * @inheritDoc
1521
- */
1522
- serialize(): any {
1523
- return {
1524
- ...super.serialize(),
1525
- quoteId: this.quoteId,
1526
- recipient: this.recipient,
1527
- vaultOwner: this.vaultOwner,
1528
- vaultId: this.vaultId.toString(10),
1529
- vaultRequiredConfirmations: this.vaultRequiredConfirmations,
1530
- vaultTokenMultipliers: this.vaultTokenMultipliers.map(val => val.toString(10)),
1531
- vaultBtcAddress: this.vaultBtcAddress,
1532
- vaultUtxo: this.vaultUtxo,
1533
- vaultUtxoValue: this.vaultUtxoValue.toString(10),
1534
- btcDestinationAddress: this.btcDestinationAddress,
1535
- btcAmount: this.btcAmount.toString(10),
1536
- btcAmountSwap: this.btcAmountSwap.toString(10),
1537
- btcAmountGas: this.btcAmountGas.toString(10),
1538
- minimumBtcFeeRate: this.minimumBtcFeeRate,
1539
- outputTotalSwap: this.outputTotalSwap.toString(10),
1540
- outputSwapToken: this.outputSwapToken,
1541
- outputTotalGas: this.outputTotalGas.toString(10),
1542
- outputGasToken: this.outputGasToken,
1543
- gasSwapFeeBtc: this.gasSwapFeeBtc.toString(10),
1544
- gasSwapFee: this.gasSwapFee.toString(10),
1545
- callerFeeShare: this.callerFeeShare.toString(10),
1546
- frontingFeeShare: this.frontingFeeShare.toString(10),
1547
- executionFeeShare: this.executionFeeShare.toString(10),
1548
- genesisSmartChainBlockHeight: this._genesisSmartChainBlockHeight,
1549
- gasPricingInfo: serializePriceInfoType(this.gasPricingInfo),
1550
- posted: this.posted,
1551
-
1552
- senderAddress: this._senderAddress,
1553
- claimTxId: this._claimTxId,
1554
- frontTxId: this._frontTxId,
1555
- data: this._data?.serialize(),
1556
- btcTxConfirmedAt: this.btcTxConfirmedAt
1557
- };
1558
- }
1559
-
1560
-
1561
- //////////////////////////////
1562
- //// Swap ticks & sync
1563
-
1564
- /**
1565
- * Used to set the txId of the bitcoin payment from the on-chain events listener
1566
- *
1567
- * @param txId
1568
- * @internal
1569
- */
1570
- async _setBitcoinTxId(txId: string) {
1571
- if(this._data==null) return;
1572
- if(txId!=this._data.btcTx.txid) return;
1573
-
1574
- if(this._senderAddress!=null) return;
1575
- const btcTx = await this.wrapper._btcRpc.getTransaction(txId);
1576
- if(btcTx==null || btcTx.inputAddresses==null) return;
1577
-
1578
- this._senderAddress = btcTx.inputAddresses[1];
1579
- }
1580
-
1581
- private btcTxLastChecked?: number;
1582
-
1583
- /**
1584
- * @internal
1585
- */
1586
- async _syncStateFromBitcoin(save?: boolean) {
1587
- if(this._data?.btcTx==null) return false;
1588
-
1589
- //Check if bitcoin payment was confirmed
1590
- this.btcTxLastChecked = Date.now();
1591
- const res = await this.getBitcoinPayment();
1592
- if(res==null) {
1593
- //Check inputs double-spent
1594
- for(let input of this._data.btcTx.ins) {
1595
- if(await this.wrapper._btcRpc.isSpent(input.txid+":"+input.vout, true)) {
1596
- if(
1597
- this._state===SpvFromBTCSwapState.SIGNED ||
1598
- this._state===SpvFromBTCSwapState.POSTED ||
1599
- this._state===SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED ||
1600
- this._state===SpvFromBTCSwapState.DECLINED
1601
- ) {
1602
- //One of the inputs was double-spent
1603
- this._state = SpvFromBTCSwapState.QUOTE_EXPIRED;
1604
- } else {
1605
- //One of the inputs was double-spent
1606
- this._state = SpvFromBTCSwapState.FAILED;
1607
- }
1608
- if(save) await this._saveAndEmit();
1609
- return true;
1610
- }
1611
- }
1612
- } else {
1613
- let needsSave = false;
1614
- if(res.inputAddresses!=null && this._senderAddress==null) {
1615
- this._senderAddress = res.inputAddresses[1];
1616
- needsSave = true;
1617
- }
1618
- if(res.confirmations>=this.vaultRequiredConfirmations) {
1619
- if(
1620
- this._state!==SpvFromBTCSwapState.BTC_TX_CONFIRMED &&
1621
- this._state!==SpvFromBTCSwapState.FRONTED &&
1622
- this._state!==SpvFromBTCSwapState.CLAIMED
1623
- ) {
1624
- this.btcTxConfirmedAt ??= Date.now();
1625
- this._state = SpvFromBTCSwapState.BTC_TX_CONFIRMED;
1626
- needsSave = true;
1627
- }
1628
- } else if(
1629
- this._state===SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED ||
1630
- this._state===SpvFromBTCSwapState.POSTED ||
1631
- this._state===SpvFromBTCSwapState.SIGNED ||
1632
- this._state===SpvFromBTCSwapState.DECLINED
1633
- ) {
1634
- this._state = SpvFromBTCSwapState.BROADCASTED;
1635
- needsSave = true;
1636
- }
1637
- if(needsSave && save) await this._saveAndEmit();
1638
- return needsSave;
1639
- }
1640
- return false;
1641
- }
1642
-
1643
- /**
1644
- * Checks the swap's state on-chain and compares it to its internal state, updates/changes it according to on-chain
1645
- * data
1646
- */
1647
- private async syncStateFromChain(): Promise<boolean> {
1648
- let changed: boolean = false;
1649
-
1650
- if(
1651
- this._state===SpvFromBTCSwapState.SIGNED ||
1652
- this._state===SpvFromBTCSwapState.POSTED ||
1653
- this._state===SpvFromBTCSwapState.BROADCASTED ||
1654
- this._state===SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED ||
1655
- this._state===SpvFromBTCSwapState.DECLINED ||
1656
- this._state===SpvFromBTCSwapState.BTC_TX_CONFIRMED
1657
- ) {
1658
- //Check BTC transaction
1659
- if(await this._syncStateFromBitcoin(false)) changed ||= true;
1660
- }
1661
-
1662
- if(this._state===SpvFromBTCSwapState.BROADCASTED || this._state===SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
1663
- if(await this._shouldCheckWithdrawalState()) {
1664
- const status = await this._contract.getWithdrawalState(this._data!, this._genesisSmartChainBlockHeight);
1665
- this.logger.debug("syncStateFromChain(): status of "+this._data!.btcTx.txid, status);
1666
- switch(status?.type) {
1667
- case SpvWithdrawalStateType.FRONTED:
1668
- this._frontTxId = status.txId;
1669
- this._state = SpvFromBTCSwapState.FRONTED;
1670
- changed ||= true;
1671
- break;
1672
- case SpvWithdrawalStateType.CLAIMED:
1673
- this._claimTxId = status.txId;
1674
- this._state = SpvFromBTCSwapState.CLAIMED;
1675
- changed ||= true;
1676
- break;
1677
- case SpvWithdrawalStateType.CLOSED:
1678
- this._state = SpvFromBTCSwapState.CLOSED;
1679
- changed ||= true;
1680
- break;
1681
- }
1682
- }
1683
- }
1684
-
1685
- if(
1686
- this._state===SpvFromBTCSwapState.CREATED ||
1687
- this._state===SpvFromBTCSwapState.SIGNED ||
1688
- this._state===SpvFromBTCSwapState.POSTED
1689
- ) {
1690
- if(this.expiry<Date.now()) {
1691
- if(this._state===SpvFromBTCSwapState.CREATED) {
1692
- this._state = SpvFromBTCSwapState.QUOTE_EXPIRED;
1693
- } else {
1694
- this._state = SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED;
1695
- }
1696
- changed ||= true;
1697
- }
1698
- }
1699
-
1700
- return changed;
1701
- }
1702
-
1703
- /**
1704
- * @inheritDoc
1705
- * @internal
1706
- */
1707
- async _sync(save?: boolean): Promise<boolean> {
1708
- const changed = await this.syncStateFromChain();
1709
- if(changed && save) await this._saveAndEmit();
1710
- return changed;
1711
- }
1712
-
1713
- /**
1714
- * @inheritDoc
1715
- * @internal
1716
- */
1717
- async _tick(save?: boolean): Promise<boolean> {
1718
- if(
1719
- this._state===SpvFromBTCSwapState.CREATED ||
1720
- this._state===SpvFromBTCSwapState.SIGNED
1721
- ) {
1722
- if(this.getQuoteExpiry()<Date.now()) {
1723
- this._state = SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED;
1724
- if(save) await this._saveAndEmit();
1725
- return true;
1726
- }
1727
- }
1728
-
1729
- if(this._state===SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED && !this.posted) {
1730
- if(this.expiry<Date.now()) {
1731
- this._state = SpvFromBTCSwapState.QUOTE_EXPIRED;
1732
- if(save) await this._saveAndEmit();
1733
- return true;
1734
- }
1735
- }
1736
-
1737
- if(this.btcTxLastChecked==null || Date.now() - this.btcTxLastChecked > 120_000) {
1738
- if (
1739
- this._state === SpvFromBTCSwapState.POSTED ||
1740
- this._state === SpvFromBTCSwapState.BROADCASTED
1741
- ) {
1742
- try {
1743
- //Check if bitcoin payment was confirmed
1744
- return await this._syncStateFromBitcoin(save);
1745
- } catch (e) {
1746
- this.logger.error("tickSwap("+this.getId()+"): ", e);
1747
- }
1748
- }
1749
- }
1750
-
1751
- return false;
1752
- }
1753
-
1754
- /**
1755
- * Checks whether an on-chain withdrawal state should be fetched for this specific swap
1756
- *
1757
- * @internal
1758
- */
1759
- async _shouldCheckWithdrawalState(frontingAddress?: string | null, vaultDataUtxo?: string | null) {
1760
- if(frontingAddress===undefined) frontingAddress = await this._contract.getFronterAddress(this.vaultOwner, this.vaultId, this._data!);
1761
- if(vaultDataUtxo===undefined) vaultDataUtxo = await this._contract.getVaultLatestUtxo(this.vaultOwner, this.vaultId);
1762
-
1763
- if(frontingAddress != null) return true; //In case the swap is fronted there will for sure be a fronted event
1764
- if(vaultDataUtxo == null) return true; //Vault UTXO is null (the vault closed)
1765
-
1766
- const [txId, _] = vaultDataUtxo.split(":");
1767
- //Don't check both txns if their txId is equal
1768
- if(this._data!.btcTx.txid===txId) return true;
1769
- const [btcTx, latestVaultTx] = await Promise.all([
1770
- this.wrapper._btcRpc.getTransaction(this._data!.btcTx.txid),
1771
- this.wrapper._btcRpc.getTransaction(txId)
1772
- ]);
1773
-
1774
- if(latestVaultTx==null || latestVaultTx.blockheight==null) {
1775
- //Something must've gone horribly wrong, the latest vault utxo tx of the vault either
1776
- // cannot be found on bitcoin network or is not even confirmed yet
1777
- this.logger.debug(`_shouldCheckWithdrawalState(): Latest vault utxo not found or not confirmed on bitcoin ${txId}`);
1778
- return false;
1779
- }
1780
-
1781
- if(btcTx!=null) {
1782
- const btcTxHeight = btcTx.blockheight;
1783
- const latestVaultTxHeight = latestVaultTx.blockheight;
1784
- //We also need to cover the case where bitcoin tx isn't confirmed yet (hence btxTxHeight==null)
1785
- if(btcTxHeight==null || latestVaultTxHeight < btcTxHeight) {
1786
- //Definitely not claimed!
1787
- this.logger.debug(`_shouldCheckWithdrawalState(): Skipped checking withdrawal state, latestVaultTxHeight: ${latestVaultTx.blockheight}, btcTxHeight: ${btcTxHeight} and not fronted!`);
1788
- return false;
1789
- }
1790
- } else {
1791
- //Definitely not claimed because the transaction was probably double-spent (or evicted from mempool)
1792
- this.logger.debug(`_shouldCheckWithdrawalState(): Skipped checking withdrawal state, btc tx probably replaced or evicted: ${this._data!.btcTx.txid} and not fronted`);
1793
- return false;
1794
- }
1795
-
1796
- return true;
1797
- }
1798
-
1799
- }
1
+ import {isISwapInit, ISwap, ISwapInit} from "../ISwap";
2
+ import {
3
+ BtcTxWithBlockheight,
4
+ ChainType,
5
+ isAbstractSigner,
6
+ SpvWithdrawalClaimedState,
7
+ SpvWithdrawalClosedState,
8
+ SpvWithdrawalFrontedState,
9
+ SpvWithdrawalState,
10
+ SpvWithdrawalStateType
11
+ } from "@atomiqlabs/base";
12
+ import {SwapType} from "../../enums/SwapType";
13
+ import {SpvFromBTCTypeDefinition, SpvFromBTCWrapper} from "./SpvFromBTCWrapper";
14
+ import {extendAbortController} from "../../utils/Utils";
15
+ import {parsePsbtTransaction, toCoinselectAddressType, toOutputScript} from "../../utils/BitcoinUtils";
16
+ import {getInputType, Transaction} from "@scure/btc-signer";
17
+ import {Buffer} from "buffer";
18
+ import {Fee} from "../../types/fees/Fee";
19
+ import {BitcoinWalletUtxo, IBitcoinWallet, isIBitcoinWallet} from "../../bitcoin/wallet/IBitcoinWallet";
20
+ import {IntermediaryAPI} from "../../intermediaries/apis/IntermediaryAPI";
21
+ import {IBTCWalletSwap} from "../IBTCWalletSwap";
22
+ import {ISwapWithGasDrop} from "../ISwapWithGasDrop";
23
+ import {
24
+ MinimalBitcoinWalletInterface,
25
+ MinimalBitcoinWalletInterfaceWithSigner
26
+ } from "../../types/wallets/MinimalBitcoinWalletInterface";
27
+ import {IClaimableSwap} from "../IClaimableSwap";
28
+ import {FeeType} from "../../enums/FeeType";
29
+ import {ppmToPercentage} from "../../types/fees/PercentagePPM";
30
+ import {TokenAmount, toTokenAmount} from "../../types/TokenAmount";
31
+ import {BitcoinTokens, BtcToken, SCToken} from "../../types/Token";
32
+ import {getLogger, LoggerType} from "../../utils/Logger";
33
+ import {timeoutPromise} from "../../utils/TimeoutUtils";
34
+ import {
35
+ deserializePriceInfoType,
36
+ isPriceInfoType,
37
+ PriceInfoType,
38
+ serializePriceInfoType
39
+ } from "../../types/PriceInfoType";
40
+ import {toBitcoinWallet} from "../../utils/BitcoinWalletUtils";
41
+ import {SwapExecutionAction, SwapExecutionActionBitcoin} from "../../types/SwapExecutionAction";
42
+
43
+ /**
44
+ * State enum for SPV vault (UTXO-controlled vault) based swaps
45
+ * @category Swaps/Bitcoin → Smart chain
46
+ */
47
+ export enum SpvFromBTCSwapState {
48
+ /**
49
+ * Catastrophic failure has occurred when processing the swap on the smart chain side,
50
+ * this implies a bug in the smart contract code or the user and intermediary deliberately
51
+ * creating a bitcoin transaction with invalid format unparsable by the smart contract.
52
+ */
53
+ CLOSED = -5,
54
+ /**
55
+ * Some of the bitcoin swap transaction inputs were double-spent, this means the swap
56
+ * has failed and no BTC was sent
57
+ */
58
+ FAILED = -4,
59
+ /**
60
+ * The intermediary (LP) declined to co-sign the submitted PSBT, hence the swap failed
61
+ */
62
+ DECLINED = -3,
63
+ /**
64
+ * Swap has expired for good and there is no way how it can be executed anymore
65
+ */
66
+ QUOTE_EXPIRED = -2,
67
+ /**
68
+ * A swap is almost expired, and it should be presented to the user as expired, though
69
+ * there is still a chance that it will be processed
70
+ */
71
+ QUOTE_SOFT_EXPIRED = -1,
72
+ /**
73
+ * Swap was created, use the {@link SpvFromBTCSwap.getFundedPsbt} or {@link SpvFromBTCSwap.getPsbt} functions
74
+ * to get the bitcoin swap PSBT that should be signed by the user's wallet and then submitted via the
75
+ * {@link SpvFromBTCSwap.submitPsbt} function.
76
+ */
77
+ CREATED = 0,
78
+ /**
79
+ * Swap bitcoin PSBT was submitted by the client to the SDK
80
+ */
81
+ SIGNED = 1,
82
+ /**
83
+ * Swap bitcoin PSBT sent to the intermediary (LP), waiting for the intermediary co-sign
84
+ * it and broadcast. You can use the {@link SpvFromBTCSwap.waitTillClaimedOrFronted}
85
+ * function to wait till the intermediary broadcasts the transaction and the transaction
86
+ * confirms.
87
+ */
88
+ POSTED = 2,
89
+ /**
90
+ * Intermediary (LP) has co-signed and broadcasted the bitcoin transaction. You can use the
91
+ * {@link SpvFromBTCSwap.waitTillClaimedOrFronted} function to wait till the transaction
92
+ * confirms.
93
+ */
94
+ BROADCASTED = 3,
95
+ /**
96
+ * Settlement on the destination smart chain was fronted and funds were already received
97
+ * by the user, even before the final settlement.
98
+ */
99
+ FRONTED = 4,
100
+ /**
101
+ * Bitcoin transaction confirmed with necessary amount of confirmations, wait for automatic
102
+ * settlement by the watchtower with the {@link waitTillClaimedOrFronted} function, or settle manually
103
+ * using the {@link FromBTCSwap.claim} or {@link FromBTCSwap.txsClaim} function.
104
+ */
105
+ BTC_TX_CONFIRMED = 5,
106
+ /**
107
+ * Swap settled on the smart chain and funds received
108
+ */
109
+ CLAIMED = 6
110
+ }
111
+
112
+ const SpvFromBTCSwapStateDescription = {
113
+ [SpvFromBTCSwapState.CLOSED]: "Catastrophic failure has occurred when processing the swap on the smart chain side, this implies a bug in the smart contract code or the user and intermediary deliberately creating a bitcoin transaction with invalid format unparsable by the smart contract.",
114
+ [SpvFromBTCSwapState.FAILED]: "Some of the bitcoin swap transaction inputs were double-spent, this means the swap has failed and no BTC was sent",
115
+ [SpvFromBTCSwapState.DECLINED]: "The intermediary (LP) declined to co-sign the submitted PSBT, hence the swap failed",
116
+ [SpvFromBTCSwapState.QUOTE_EXPIRED]: "Swap has expired for good and there is no way how it can be executed anymore",
117
+ [SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED]: "A swap is almost expired, and it should be presented to the user as expired, though there is still a chance that it will be processed",
118
+ [SpvFromBTCSwapState.CREATED]: "Swap was created, get the bitcoin swap PSBT that should be signed by the user's wallet and then submit it back to the SDK.",
119
+ [SpvFromBTCSwapState.SIGNED]: "Swap bitcoin PSBT was submitted by the client to the SDK",
120
+ [SpvFromBTCSwapState.POSTED]: "Swap bitcoin PSBT sent to the intermediary (LP), waiting for the intermediary co-sign it and broadcast.",
121
+ [SpvFromBTCSwapState.BROADCASTED]: "Intermediary (LP) has co-signed and broadcasted the bitcoin transaction.",
122
+ [SpvFromBTCSwapState.FRONTED]: "Settlement on the destination smart chain was fronted and funds were already received by the user, even before the final settlement.",
123
+ [SpvFromBTCSwapState.BTC_TX_CONFIRMED]: "Bitcoin transaction confirmed with necessary amount of confirmations, wait for automatic settlement by the watchtower or settle manually.",
124
+ [SpvFromBTCSwapState.CLAIMED]: "Swap settled on the smart chain and funds received"
125
+ }
126
+
127
+ export type SpvFromBTCSwapInit = ISwapInit & {
128
+ quoteId: string;
129
+ recipient: string;
130
+ vaultOwner: string;
131
+ vaultId: bigint;
132
+ vaultRequiredConfirmations: number;
133
+ vaultTokenMultipliers: bigint[];
134
+ vaultBtcAddress: string;
135
+ vaultUtxo: string;
136
+ vaultUtxoValue: bigint;
137
+ btcDestinationAddress: string;
138
+ btcAmount: bigint;
139
+ btcAmountSwap: bigint;
140
+ btcAmountGas: bigint;
141
+ minimumBtcFeeRate: number;
142
+ outputTotalSwap: bigint;
143
+ outputSwapToken: string;
144
+ outputTotalGas: bigint;
145
+ outputGasToken: string;
146
+ gasSwapFeeBtc: bigint;
147
+ gasSwapFee: bigint;
148
+ callerFeeShare: bigint;
149
+ frontingFeeShare: bigint;
150
+ executionFeeShare: bigint;
151
+ genesisSmartChainBlockHeight: number;
152
+ gasPricingInfo?: PriceInfoType;
153
+ };
154
+
155
+ export function isSpvFromBTCSwapInit(obj: any): obj is SpvFromBTCSwapInit {
156
+ return typeof obj === "object" &&
157
+ typeof(obj.quoteId)==="string" &&
158
+ typeof(obj.recipient)==="string" &&
159
+ typeof(obj.vaultOwner)==="string" &&
160
+ typeof(obj.vaultId)==="bigint" &&
161
+ typeof(obj.vaultRequiredConfirmations)==="number" &&
162
+ Array.isArray(obj.vaultTokenMultipliers) && obj.vaultTokenMultipliers.reduce((prev: boolean, curr: any) => prev && typeof(curr)==="bigint", true) &&
163
+ typeof(obj.vaultBtcAddress)==="string" &&
164
+ typeof(obj.vaultUtxo)==="string" &&
165
+ typeof(obj.vaultUtxoValue)==="bigint" &&
166
+ typeof(obj.btcDestinationAddress)==="string" &&
167
+ typeof(obj.btcAmount)==="bigint" &&
168
+ typeof(obj.btcAmountSwap)==="bigint" &&
169
+ typeof(obj.btcAmountGas)==="bigint" &&
170
+ typeof(obj.minimumBtcFeeRate)==="number" &&
171
+ typeof(obj.outputTotalSwap)==="bigint" &&
172
+ typeof(obj.outputSwapToken)==="string" &&
173
+ typeof(obj.outputTotalGas)==="bigint" &&
174
+ typeof(obj.outputGasToken)==="string" &&
175
+ typeof(obj.gasSwapFeeBtc)==="bigint" &&
176
+ typeof(obj.gasSwapFee)==="bigint" &&
177
+ typeof(obj.callerFeeShare)==="bigint" &&
178
+ typeof(obj.frontingFeeShare)==="bigint" &&
179
+ typeof(obj.executionFeeShare)==="bigint" &&
180
+ typeof(obj.genesisSmartChainBlockHeight)==="number" &&
181
+ (obj.gasPricingInfo==null || isPriceInfoType(obj.gasPricingInfo)) &&
182
+ isISwapInit(obj);
183
+ }
184
+
185
+ /**
186
+ * New spv vault (UTXO-controlled vault) based swaps for Bitcoin -> Smart chain swaps not requiring
187
+ * any initiation on the destination chain, and with the added possibility for the user to receive
188
+ * a native token on the destination chain as part of the swap (a "gas drop" feature).
189
+ *
190
+ * @category Swaps/Bitcoin → Smart chain
191
+ */
192
+ export class SpvFromBTCSwap<T extends ChainType>
193
+ extends ISwap<T, SpvFromBTCTypeDefinition<T>>
194
+ implements IBTCWalletSwap, ISwapWithGasDrop<T>, IClaimableSwap<T, SpvFromBTCTypeDefinition<T>, SpvFromBTCSwapState> {
195
+
196
+ protected readonly currentVersion: number = 2;
197
+
198
+ readonly TYPE: SwapType.SPV_VAULT_FROM_BTC = SwapType.SPV_VAULT_FROM_BTC;
199
+
200
+ /**
201
+ * @internal
202
+ */
203
+ protected readonly swapStateDescription = SpvFromBTCSwapStateDescription;
204
+ /**
205
+ * @internal
206
+ */
207
+ protected readonly swapStateName = (state: number) => SpvFromBTCSwapState[state];
208
+ /**
209
+ * @inheritDoc
210
+ * @internal
211
+ */
212
+ protected readonly logger: LoggerType;
213
+
214
+ private readonly quoteId: string;
215
+ private readonly recipient: string;
216
+
217
+ private readonly vaultOwner: string;
218
+ private readonly vaultId: bigint;
219
+ private readonly vaultRequiredConfirmations: number;
220
+ private readonly vaultTokenMultipliers: bigint[];
221
+
222
+ private readonly vaultBtcAddress: string;
223
+ private readonly vaultUtxo: string;
224
+ private readonly vaultUtxoValue: bigint;
225
+
226
+ private readonly btcDestinationAddress: string;
227
+ private readonly btcAmount: bigint;
228
+ private readonly btcAmountSwap: bigint;
229
+ private readonly btcAmountGas: bigint;
230
+
231
+ private readonly outputTotalSwap: bigint;
232
+ private readonly outputSwapToken: string;
233
+ private readonly outputTotalGas: bigint;
234
+ private readonly outputGasToken: string;
235
+
236
+ private readonly gasSwapFeeBtc: bigint;
237
+ private readonly gasSwapFee: bigint;
238
+
239
+ private readonly callerFeeShare: bigint;
240
+ private readonly frontingFeeShare: bigint;
241
+ private readonly executionFeeShare: bigint;
242
+
243
+ private readonly gasPricingInfo?: PriceInfoType;
244
+
245
+ private posted?: boolean;
246
+
247
+ /**
248
+ * @internal
249
+ */
250
+ readonly _genesisSmartChainBlockHeight: number;
251
+ /**
252
+ * @internal
253
+ */
254
+ _senderAddress?: string;
255
+ /**
256
+ * @internal
257
+ */
258
+ _claimTxId?: string;
259
+ /**
260
+ * @internal
261
+ */
262
+ _frontTxId?: string;
263
+ /**
264
+ * @internal
265
+ */
266
+ _data?: T["SpvVaultWithdrawalData"];
267
+
268
+ /**
269
+ * Minimum fee rate in sats/vB that the input bitcoin transaction needs to pay
270
+ */
271
+ readonly minimumBtcFeeRate: number;
272
+
273
+ /**
274
+ * Time at which the SDK realized the bitcoin transaction was confirmed
275
+ * @private
276
+ */
277
+ private btcTxConfirmedAt?: number;
278
+
279
+ private _contract: T["SpvVaultContract"];
280
+
281
+ constructor(wrapper: SpvFromBTCWrapper<T>, init: SpvFromBTCSwapInit);
282
+ constructor(wrapper: SpvFromBTCWrapper<T>, obj: any);
283
+ constructor(wrapper: SpvFromBTCWrapper<T>, initOrObject: SpvFromBTCSwapInit | any) {
284
+ if(isSpvFromBTCSwapInit(initOrObject) && initOrObject.url!=null) initOrObject.url += "/frombtc_spv";
285
+ super(wrapper, initOrObject);
286
+ if(isSpvFromBTCSwapInit(initOrObject)) {
287
+ this._state = SpvFromBTCSwapState.CREATED;
288
+ this.quoteId = initOrObject.quoteId;
289
+ this.recipient = initOrObject.recipient;
290
+ this.vaultOwner = initOrObject.vaultOwner;
291
+ this.vaultId = initOrObject.vaultId;
292
+ this.vaultRequiredConfirmations = initOrObject.vaultRequiredConfirmations;
293
+ this.vaultTokenMultipliers = initOrObject.vaultTokenMultipliers;
294
+ this.vaultBtcAddress = initOrObject.vaultBtcAddress;
295
+ this.vaultUtxo = initOrObject.vaultUtxo;
296
+ this.vaultUtxoValue = initOrObject.vaultUtxoValue;
297
+ this.btcDestinationAddress = initOrObject.btcDestinationAddress;
298
+ this.btcAmount = initOrObject.btcAmount;
299
+ this.btcAmountSwap = initOrObject.btcAmountSwap;
300
+ this.btcAmountGas = initOrObject.btcAmountGas;
301
+ this.minimumBtcFeeRate = initOrObject.minimumBtcFeeRate;
302
+ this.outputTotalSwap = initOrObject.outputTotalSwap;
303
+ this.outputSwapToken = initOrObject.outputSwapToken;
304
+ this.outputTotalGas = initOrObject.outputTotalGas;
305
+ this.outputGasToken = initOrObject.outputGasToken;
306
+ this.gasSwapFeeBtc = initOrObject.gasSwapFeeBtc;
307
+ this.gasSwapFee = initOrObject.gasSwapFee;
308
+ this.callerFeeShare = initOrObject.callerFeeShare;
309
+ this.frontingFeeShare = initOrObject.frontingFeeShare;
310
+ this.executionFeeShare = initOrObject.executionFeeShare;
311
+ this._genesisSmartChainBlockHeight = initOrObject.genesisSmartChainBlockHeight;
312
+ this.gasPricingInfo = initOrObject.gasPricingInfo;
313
+ const vaultAddressType = toCoinselectAddressType(toOutputScript(this.wrapper._options.bitcoinNetwork, this.vaultBtcAddress));
314
+ if(vaultAddressType!=="p2tr" && vaultAddressType!=="p2wpkh" && vaultAddressType!=="p2wsh")
315
+ throw new Error("Vault address type must be of witness type: p2tr, p2wpkh, p2wsh");
316
+ } else {
317
+ this.quoteId = initOrObject.quoteId;
318
+ this.recipient = initOrObject.recipient;
319
+ this.vaultOwner = initOrObject.vaultOwner;
320
+ this.vaultId = BigInt(initOrObject.vaultId);
321
+ this.vaultRequiredConfirmations = initOrObject.vaultRequiredConfirmations;
322
+ this.vaultTokenMultipliers = initOrObject.vaultTokenMultipliers.map((val: string) => BigInt(val));
323
+ this.vaultBtcAddress = initOrObject.vaultBtcAddress;
324
+ this.vaultUtxo = initOrObject.vaultUtxo;
325
+ this.vaultUtxoValue = BigInt(initOrObject.vaultUtxoValue);
326
+ this.btcDestinationAddress = initOrObject.btcDestinationAddress;
327
+ this.btcAmount = BigInt(initOrObject.btcAmount);
328
+ this.btcAmountSwap = BigInt(initOrObject.btcAmountSwap);
329
+ this.btcAmountGas = BigInt(initOrObject.btcAmountGas);
330
+ this.minimumBtcFeeRate = initOrObject.minimumBtcFeeRate;
331
+ this.outputTotalSwap = BigInt(initOrObject.outputTotalSwap);
332
+ this.outputSwapToken = initOrObject.outputSwapToken;
333
+ this.outputTotalGas = BigInt(initOrObject.outputTotalGas);
334
+ this.outputGasToken = initOrObject.outputGasToken;
335
+ this.gasSwapFeeBtc = BigInt(initOrObject.gasSwapFeeBtc);
336
+ this.gasSwapFee = BigInt(initOrObject.gasSwapFee);
337
+ this.callerFeeShare = BigInt(initOrObject.callerFeeShare);
338
+ this.frontingFeeShare = BigInt(initOrObject.frontingFeeShare);
339
+ this.executionFeeShare = BigInt(initOrObject.executionFeeShare);
340
+ this._genesisSmartChainBlockHeight = initOrObject.genesisSmartChainBlockHeight;
341
+ this._senderAddress = initOrObject.senderAddress;
342
+ this._claimTxId = initOrObject.claimTxId;
343
+ this._frontTxId = initOrObject.frontTxId;
344
+ this.gasPricingInfo = deserializePriceInfoType(initOrObject.gasPricingInfo);
345
+ this.btcTxConfirmedAt = initOrObject.btcTxConfirmedAt;
346
+ this.posted = initOrObject.posted;
347
+ if(initOrObject.data!=null) this._data = new (this.wrapper._spvWithdrawalDataDeserializer(this._contractVersion))(initOrObject.data);
348
+ }
349
+ this.tryCalculateSwapFee();
350
+ this.logger = getLogger("SPVFromBTC("+this.getId()+"): ");
351
+
352
+ this._contract = wrapper._contract(this._contractVersion);
353
+ }
354
+
355
+ /**
356
+ * @inheritDoc
357
+ * @internal
358
+ */
359
+ protected upgradeVersion() {
360
+ if(this.version===1) {
361
+ this.posted = this.initiated && this._data!=null;
362
+ this.version = 2;
363
+ }
364
+ }
365
+
366
+ /**
367
+ * @inheritDoc
368
+ * @internal
369
+ */
370
+ protected tryCalculateSwapFee() {
371
+ if(this.swapFeeBtc==null && this.swapFee!=null) {
372
+ this.swapFeeBtc = this.swapFee * this.btcAmountSwap / this.getOutputWithoutFee().rawAmount;
373
+ }
374
+
375
+ if(this.pricingInfo!=null && this.pricingInfo.swapPriceUSatPerToken==null) {
376
+ const priceUsdPerBtc = this.pricingInfo.realPriceUsdPerBitcoin;
377
+ this.pricingInfo = this.wrapper._prices.recomputePriceInfoReceive(
378
+ this.chainIdentifier,
379
+ this.btcAmountSwap,
380
+ this.pricingInfo.satsBaseFee,
381
+ this.pricingInfo.feePPM,
382
+ this.getOutputWithoutFee().rawAmount,
383
+ this.outputSwapToken
384
+ );
385
+ this.pricingInfo.realPriceUsdPerBitcoin = priceUsdPerBtc;
386
+ }
387
+ }
388
+
389
+
390
+ //////////////////////////////
391
+ //// Pricing
392
+
393
+ /**
394
+ * @inheritDoc
395
+ */
396
+ async refreshPriceData(): Promise<void> {
397
+ if(this.pricingInfo==null) return;
398
+ const usdPricePerBtc = this.pricingInfo.realPriceUsdPerBitcoin;
399
+ this.pricingInfo = await this.wrapper._prices.isValidAmountReceive(
400
+ this.chainIdentifier,
401
+ this.btcAmountSwap,
402
+ this.pricingInfo.satsBaseFee,
403
+ this.pricingInfo.feePPM,
404
+ this.getOutputWithoutFee().rawAmount,
405
+ this.outputSwapToken,
406
+ undefined,
407
+ undefined,
408
+ this.swapFeeBtc
409
+ );
410
+ this.pricingInfo.realPriceUsdPerBitcoin = usdPricePerBtc;
411
+ }
412
+
413
+
414
+ //////////////////////////////
415
+ //// Getters & utils
416
+
417
+ /**
418
+ * @inheritDoc
419
+ * @internal
420
+ */
421
+ _getInitiator(): string {
422
+ return this.recipient;
423
+ }
424
+
425
+ /**
426
+ * @inheritDoc
427
+ * @internal
428
+ */
429
+ _getEscrowHash(): string | null {
430
+ return this._data?.btcTx?.txid ?? null;
431
+ }
432
+
433
+ /**
434
+ * @inheritDoc
435
+ */
436
+ getId(): string {
437
+ return this.quoteId+this._randomNonce;
438
+ }
439
+
440
+ /**
441
+ * @inheritDoc
442
+ */
443
+ getQuoteExpiry(): number {
444
+ return this.expiry - 20*1000;
445
+ }
446
+
447
+ /**
448
+ * @inheritDoc
449
+ * @internal
450
+ */
451
+ _verifyQuoteDefinitelyExpired(): Promise<boolean> {
452
+ return Promise.resolve(this.expiry<Date.now());
453
+ }
454
+
455
+ /**
456
+ * @inheritDoc
457
+ * @internal
458
+ */
459
+ _verifyQuoteValid(): Promise<boolean> {
460
+ return Promise.resolve(this.expiry>Date.now() && (this._state===SpvFromBTCSwapState.CREATED || this._state===SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED));
461
+ }
462
+
463
+ /**
464
+ * @inheritDoc
465
+ */
466
+ getOutputAddress(): string | null {
467
+ return this.recipient;
468
+ }
469
+
470
+ /**
471
+ * @inheritDoc
472
+ */
473
+ getOutputTxId(): string | null {
474
+ return this._frontTxId ?? this._claimTxId ?? null;
475
+ }
476
+
477
+ /**
478
+ * @inheritDoc
479
+ */
480
+ getInputAddress(): string | null {
481
+ return this._senderAddress ?? null;
482
+ }
483
+
484
+ /**
485
+ * @inheritDoc
486
+ */
487
+ getInputTxId(): string | null {
488
+ return this._data?.btcTx?.txid ?? null;
489
+ }
490
+
491
+ /**
492
+ * @inheritDoc
493
+ */
494
+ requiresAction(): boolean {
495
+ return this._state===SpvFromBTCSwapState.BTC_TX_CONFIRMED;
496
+ }
497
+
498
+ /**
499
+ * @inheritDoc
500
+ */
501
+ isFinished(): boolean {
502
+ return this._state===SpvFromBTCSwapState.CLAIMED || this._state===SpvFromBTCSwapState.QUOTE_EXPIRED || this._state===SpvFromBTCSwapState.FAILED;
503
+ }
504
+
505
+ /**
506
+ * @inheritDoc
507
+ */
508
+ isClaimable(): boolean {
509
+ return this._state===SpvFromBTCSwapState.BTC_TX_CONFIRMED;
510
+ }
511
+
512
+ /**
513
+ * @inheritDoc
514
+ */
515
+ isSuccessful(): boolean {
516
+ return this._state===SpvFromBTCSwapState.FRONTED || this._state===SpvFromBTCSwapState.CLAIMED;
517
+ }
518
+
519
+ /**
520
+ * @inheritDoc
521
+ */
522
+ isFailed(): boolean {
523
+ return this._state===SpvFromBTCSwapState.FAILED || this._state===SpvFromBTCSwapState.DECLINED || this._state===SpvFromBTCSwapState.CLOSED;
524
+ }
525
+
526
+ /**
527
+ * @inheritDoc
528
+ */
529
+ isInProgress(): boolean {
530
+ return this._state===SpvFromBTCSwapState.POSTED ||
531
+ this._state===SpvFromBTCSwapState.BROADCASTED ||
532
+ this._state===SpvFromBTCSwapState.BTC_TX_CONFIRMED;
533
+ }
534
+
535
+ /**
536
+ * @inheritDoc
537
+ */
538
+ isQuoteExpired(): boolean {
539
+ return this._state===SpvFromBTCSwapState.QUOTE_EXPIRED;
540
+ }
541
+
542
+ /**
543
+ * @inheritDoc
544
+ */
545
+ isQuoteSoftExpired(): boolean {
546
+ return this._state===SpvFromBTCSwapState.QUOTE_EXPIRED || this._state===SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED;
547
+ }
548
+
549
+ /**
550
+ * Returns the data about used spv vault (UTXO-controlled vault) to perform the swap
551
+ */
552
+ getSpvVaultData(): {
553
+ owner: string,
554
+ vaultId: bigint,
555
+ utxo: string
556
+ } {
557
+ return {
558
+ owner: this.vaultOwner,
559
+ vaultId: this.vaultId,
560
+ utxo: this.vaultUtxo
561
+ }
562
+ }
563
+
564
+
565
+ //////////////////////////////
566
+ //// Amounts & fees
567
+
568
+ /**
569
+ * Returns the input BTC amount in sats without any fees
570
+ *
571
+ * @internal
572
+ */
573
+ protected getInputSwapAmountWithoutFee(): bigint {
574
+ return (this.btcAmountSwap - this.swapFeeBtc) * 100_000n / (100_000n + this.callerFeeShare + this.frontingFeeShare + this.executionFeeShare);
575
+ }
576
+
577
+ /**
578
+ * Returns the input gas BTC amount in sats without any fees
579
+ *
580
+ * @internal
581
+ */
582
+ protected getInputGasAmountWithoutFee(): bigint {
583
+ return (this.btcAmountGas - this.gasSwapFeeBtc) * 100_000n / (100_000n + this.callerFeeShare + this.frontingFeeShare);
584
+ }
585
+
586
+ /**
587
+ * Returns to total input BTC amount in sats without any fees (this is BTC amount for the swap + BTC amount
588
+ * for the gas drop).
589
+ *
590
+ * @internal
591
+ */
592
+ protected getInputAmountWithoutFee(): bigint {
593
+ return this.getInputSwapAmountWithoutFee() + this.getInputGasAmountWithoutFee();
594
+ }
595
+
596
+ /**
597
+ * Returns the swap output amount without any fees, this value is therefore always higher than
598
+ * the actual received output.
599
+ *
600
+ * @internal
601
+ */
602
+ protected getOutputWithoutFee(): TokenAmount<SCToken<T["ChainId"]>, true> {
603
+ return toTokenAmount(
604
+ (this.outputTotalSwap * (100_000n + this.callerFeeShare + this.frontingFeeShare + this.executionFeeShare) / 100_000n) + (this.swapFee ?? 0n),
605
+ this.wrapper._tokens[this.outputSwapToken], this.wrapper._prices, this.pricingInfo
606
+ );
607
+ }
608
+
609
+ /**
610
+ * Returns the swap fee charged by the intermediary (LP) on this swap
611
+ *
612
+ * @internal
613
+ */
614
+ protected getSwapFee(): Fee<T["ChainId"], BtcToken<false>, SCToken<T["ChainId"]>> {
615
+ if(this.pricingInfo==null) throw new Error("No pricing info known, cannot estimate fee!");
616
+
617
+ const outputToken = this.wrapper._tokens[this.outputSwapToken];
618
+ const gasSwapFeeInOutputToken = this.gasSwapFeeBtc
619
+ * (10n ** BigInt(outputToken.decimals))
620
+ * 1_000_000n
621
+ / this.pricingInfo.swapPriceUSatPerToken;
622
+
623
+ const feeWithoutBaseFee = this.swapFeeBtc - this.pricingInfo.satsBaseFee;
624
+ const swapFeePPM = feeWithoutBaseFee * 1000000n / (this.btcAmount - this.swapFeeBtc - this.gasSwapFeeBtc);
625
+
626
+ const amountInSrcToken = toTokenAmount(
627
+ this.swapFeeBtc + this.gasSwapFeeBtc, BitcoinTokens.BTC, this.wrapper._prices, this.pricingInfo
628
+ );
629
+ return {
630
+ amountInSrcToken,
631
+ amountInDstToken: toTokenAmount(this.swapFee + gasSwapFeeInOutputToken, outputToken, this.wrapper._prices, this.pricingInfo),
632
+ currentUsdValue: amountInSrcToken.currentUsdValue,
633
+ usdValue: amountInSrcToken.usdValue,
634
+ pastUsdValue: amountInSrcToken.pastUsdValue,
635
+ composition: {
636
+ base: toTokenAmount(this.pricingInfo.satsBaseFee, BitcoinTokens.BTC, this.wrapper._prices, this.pricingInfo),
637
+ percentage: ppmToPercentage(swapFeePPM)
638
+ }
639
+ };
640
+ }
641
+
642
+ /**
643
+ * Returns the fee to be paid to watchtowers on the destination chain to automatically
644
+ * process and settle this swap without requiring any user interaction
645
+ *
646
+ * @internal
647
+ */
648
+ protected getWatchtowerFee(): Fee<T["ChainId"], BtcToken<false>, SCToken<T["ChainId"]>> {
649
+ if(this.pricingInfo==null) throw new Error("No pricing info known, cannot estimate fee!");
650
+
651
+ const totalFeeShare = this.callerFeeShare + this.frontingFeeShare;
652
+ const outputToken = this.wrapper._tokens[this.outputSwapToken];
653
+ const watchtowerFeeInOutputToken = this.getInputGasAmountWithoutFee() * totalFeeShare
654
+ * (10n ** BigInt(outputToken.decimals))
655
+ * 1_000_000n
656
+ / this.pricingInfo.swapPriceUSatPerToken
657
+ / 100_000n;
658
+ const feeBtc = this.getInputAmountWithoutFee() * (totalFeeShare + this.executionFeeShare) / 100_000n;
659
+ const amountInSrcToken = toTokenAmount(feeBtc, BitcoinTokens.BTC, this.wrapper._prices, this.pricingInfo);
660
+ return {
661
+ amountInSrcToken,
662
+ amountInDstToken: toTokenAmount(
663
+ (this.outputTotalSwap * (totalFeeShare + this.executionFeeShare) / 100_000n) + watchtowerFeeInOutputToken,
664
+ outputToken, this.wrapper._prices, this.pricingInfo
665
+ ),
666
+ currentUsdValue: amountInSrcToken.currentUsdValue,
667
+ usdValue: amountInSrcToken.usdValue,
668
+ pastUsdValue: amountInSrcToken.pastUsdValue
669
+ };
670
+ }
671
+
672
+ /**
673
+ * @inheritDoc
674
+ */
675
+ getFee(): Fee<T["ChainId"], BtcToken<false>, SCToken<T["ChainId"]>> {
676
+ const swapFee = this.getSwapFee();
677
+ const watchtowerFee = this.getWatchtowerFee();
678
+
679
+ const amountInSrcToken = toTokenAmount(
680
+ swapFee.amountInSrcToken.rawAmount + watchtowerFee.amountInSrcToken.rawAmount,
681
+ BitcoinTokens.BTC, this.wrapper._prices, this.pricingInfo
682
+ );
683
+ return {
684
+ amountInSrcToken,
685
+ amountInDstToken: toTokenAmount(
686
+ swapFee.amountInDstToken.rawAmount + watchtowerFee.amountInDstToken.rawAmount,
687
+ this.wrapper._tokens[this.outputSwapToken], this.wrapper._prices, this.pricingInfo
688
+ ),
689
+ currentUsdValue: amountInSrcToken.currentUsdValue,
690
+ usdValue: amountInSrcToken.usdValue,
691
+ pastUsdValue: amountInSrcToken.pastUsdValue
692
+ };
693
+ }
694
+
695
+ /**
696
+ * @inheritDoc
697
+ */
698
+ getFeeBreakdown(): [
699
+ {type: FeeType.SWAP, fee: Fee<T["ChainId"], BtcToken<false>, SCToken<T["ChainId"]>>},
700
+ {type: FeeType.NETWORK_OUTPUT, fee: Fee<T["ChainId"], BtcToken<false>, SCToken<T["ChainId"]>>}
701
+ ] {
702
+ return [
703
+ {
704
+ type: FeeType.SWAP,
705
+ fee: this.getSwapFee()
706
+ },
707
+ {
708
+ type: FeeType.NETWORK_OUTPUT,
709
+ fee: this.getWatchtowerFee()
710
+ }
711
+ ];
712
+ }
713
+
714
+ /**
715
+ * @inheritDoc
716
+ */
717
+ getOutputToken(): SCToken<T["ChainId"]> {
718
+ return this.wrapper._tokens[this.outputSwapToken];
719
+ }
720
+
721
+ /**
722
+ * @inheritDoc
723
+ */
724
+ getOutput(): TokenAmount<SCToken<T["ChainId"]>, true> {
725
+ return toTokenAmount(this.outputTotalSwap, this.wrapper._tokens[this.outputSwapToken], this.wrapper._prices, this.pricingInfo);
726
+ }
727
+
728
+ /**
729
+ * @inheritDoc
730
+ */
731
+ getGasDropOutput(): TokenAmount<SCToken<T["ChainId"]>, true> {
732
+ return toTokenAmount(this.outputTotalGas, this.wrapper._tokens[this.outputGasToken], this.wrapper._prices, this.gasPricingInfo);
733
+ }
734
+
735
+ /**
736
+ * @inheritDoc
737
+ */
738
+ getInputWithoutFee(): TokenAmount<BtcToken<false>, true> {
739
+ return toTokenAmount(this.getInputAmountWithoutFee(), BitcoinTokens.BTC, this.wrapper._prices, this.pricingInfo);
740
+ }
741
+
742
+ /**
743
+ * @inheritDoc
744
+ */
745
+ getInputToken(): BtcToken<false> {
746
+ return BitcoinTokens.BTC;
747
+ }
748
+
749
+ /**
750
+ * @inheritDoc
751
+ */
752
+ getInput(): TokenAmount<BtcToken<false>, true> {
753
+ return toTokenAmount(this.btcAmount, BitcoinTokens.BTC, this.wrapper._prices, this.pricingInfo);
754
+ }
755
+
756
+
757
+ //////////////////////////////
758
+ //// Bitcoin tx
759
+
760
+ /**
761
+ * @inheritDoc
762
+ */
763
+ getRequiredConfirmationsCount(): number {
764
+ return this.vaultRequiredConfirmations;
765
+ }
766
+
767
+ /**
768
+ * Returns raw transaction details that can be used to manually create a swap PSBT. It is better to use
769
+ * the {@link getPsbt} or {@link getFundedPsbt} function retrieve an already prepared PSBT.
770
+ */
771
+ async getTransactionDetails(): Promise<{
772
+ in0txid: string,
773
+ in0vout: number,
774
+ in0sequence: number,
775
+ vaultAmount: bigint,
776
+ vaultScript: Uint8Array,
777
+ in1sequence: number,
778
+ out1script: Uint8Array,
779
+ out2amount: bigint,
780
+ out2script: Uint8Array,
781
+ locktime: number
782
+ }> {
783
+ const [txId, voutStr] = this.vaultUtxo.split(":");
784
+
785
+ const vaultScript = toOutputScript(this.wrapper._options.bitcoinNetwork, this.vaultBtcAddress);
786
+
787
+ const out2script = toOutputScript(this.wrapper._options.bitcoinNetwork, this.btcDestinationAddress);
788
+
789
+ const opReturnData = this._contract.toOpReturnData(
790
+ this.recipient,
791
+ [
792
+ this.outputTotalSwap / this.vaultTokenMultipliers[0],
793
+ this.outputTotalGas / this.vaultTokenMultipliers[1]
794
+ ]
795
+ );
796
+ const out1script = Buffer.concat([
797
+ opReturnData.length > 75 ? Buffer.from([0x6a, 0x4c, opReturnData.length]) : Buffer.from([0x6a, opReturnData.length]),
798
+ opReturnData
799
+ ]);
800
+
801
+ if(this.callerFeeShare<0n || this.callerFeeShare>0xFFFFFn) throw new Error("Caller fee out of bounds!");
802
+ if(this.frontingFeeShare<0n || this.frontingFeeShare>0xFFFFFn) throw new Error("Fronting fee out of bounds!");
803
+ if(this.executionFeeShare<0n || this.executionFeeShare>0xFFFFFn) throw new Error("Execution fee out of bounds!");
804
+
805
+ const nSequence0 = 0x80000000n | (this.callerFeeShare & 0xFFFFFn) | (this.frontingFeeShare & 0b1111_1111_1100_0000_0000n) << 10n;
806
+ const nSequence1 = 0x80000000n | (this.executionFeeShare & 0xFFFFFn) | (this.frontingFeeShare & 0b0000_0000_0011_1111_1111n) << 20n;
807
+
808
+ return {
809
+ in0txid: txId,
810
+ in0vout: parseInt(voutStr),
811
+ in0sequence: Number(nSequence0),
812
+ vaultAmount: this.vaultUtxoValue,
813
+ vaultScript,
814
+ in1sequence: Number(nSequence1),
815
+ out1script,
816
+ out2amount: this.btcAmount,
817
+ out2script,
818
+ locktime: 500_000_000 + Math.floor(Math.random() * 1_000_000_000) //Use this as a random salt to make the btc txId unique!
819
+ };
820
+ }
821
+
822
+ /**
823
+ * Returns the raw PSBT (not funded), the wallet should fund the PSBT (add its inputs) and importantly **set the nSequence field of the
824
+ * 2nd input** (input 1 - indexing from 0) to the value returned in `in1sequence`, sign the PSBT and then pass
825
+ * it back to the swap with {@link submitPsbt} function.
826
+ */
827
+ async getPsbt(): Promise<{
828
+ psbt: Transaction,
829
+ psbtHex: string,
830
+ psbtBase64: string,
831
+ in1sequence: number
832
+ }> {
833
+ const res = await this.getTransactionDetails();
834
+ const psbt = new Transaction({
835
+ allowUnknownOutputs: true,
836
+ allowLegacyWitnessUtxo: true,
837
+ lockTime: res.locktime
838
+ });
839
+ psbt.addInput({
840
+ txid: res.in0txid,
841
+ index: res.in0vout,
842
+ witnessUtxo: {
843
+ amount: res.vaultAmount,
844
+ script: res.vaultScript
845
+ },
846
+ sequence: res.in0sequence
847
+ });
848
+ psbt.addOutput({
849
+ amount: res.vaultAmount,
850
+ script: res.vaultScript
851
+ });
852
+ psbt.addOutput({
853
+ amount: 0n,
854
+ script: res.out1script
855
+ });
856
+ psbt.addOutput({
857
+ amount: res.out2amount,
858
+ script: res.out2script
859
+ });
860
+ const serializedPsbt = Buffer.from(psbt.toPSBT());
861
+ return {
862
+ psbt,
863
+ psbtHex: serializedPsbt.toString("hex"),
864
+ psbtBase64: serializedPsbt.toString("base64"),
865
+ in1sequence: res.in1sequence
866
+ };
867
+ }
868
+
869
+ /**
870
+ * Returns the PSBT that is already funded with wallet's UTXOs (runs a coin-selection algorithm to choose UTXOs to use),
871
+ * also returns inputs indices that need to be signed by the wallet before submitting the PSBT back to the SDK with
872
+ * {@link submitPsbt}
873
+ *
874
+ * @remarks
875
+ * Note that when passing the `feeRate` argument, the fee must be at least {@link minimumBtcFeeRate} sats/vB.
876
+ *
877
+ * @param _bitcoinWallet Sender's bitcoin wallet
878
+ * @param feeRate Optional fee rate in sats/vB for the transaction
879
+ * @param additionalOutputs additional outputs to add to the PSBT - can be used to collect fees from users
880
+ * @param utxos Pre-fetched list of UTXOs to spend from
881
+ * @param spendFully Instructs the wallet to spend all the passed UTXOs in the transaction without creating any
882
+ * change output, if the `feeRate` is passed, it will also enforce that the feeRate in sats/vB for the resulting
883
+ * transaction is not more than 50% and 10 sats/vB larger (considering also the CPFP adjustments)
884
+ */
885
+ async getFundedPsbt(
886
+ _bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface,
887
+ feeRate?: number,
888
+ additionalOutputs?: ({amount: bigint, outputScript: Uint8Array} | {amount: bigint, address: string})[],
889
+ utxos?: BitcoinWalletUtxo[],
890
+ spendFully?: boolean
891
+ ): Promise<{
892
+ psbt: Transaction,
893
+ psbtHex: string,
894
+ psbtBase64: string,
895
+ signInputs: number[]
896
+ }> {
897
+ const bitcoinWallet: IBitcoinWallet = toBitcoinWallet(_bitcoinWallet, this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork);
898
+ if(feeRate!=null) {
899
+ if(feeRate<this.minimumBtcFeeRate) throw new Error("Bitcoin tx fee needs to be at least "+this.minimumBtcFeeRate+" sats/vB");
900
+ } else {
901
+ feeRate = Math.max(this.minimumBtcFeeRate, await bitcoinWallet.getFeeRate());
902
+ }
903
+ let {psbt, in1sequence} = await this.getPsbt();
904
+ if(additionalOutputs!=null) additionalOutputs.forEach(output => {
905
+ psbt.addOutput({
906
+ amount: output.amount,
907
+ script: (output as {outputScript: Uint8Array}).outputScript ?? toOutputScript(this.wrapper._options.bitcoinNetwork, (output as {address: string}).address)
908
+ });
909
+ });
910
+ psbt = await bitcoinWallet.fundPsbt(psbt, feeRate, utxos, spendFully);
911
+ psbt.updateInput(1, {sequence: in1sequence});
912
+ //Sign every input except the first one
913
+ const signInputs: number[] = [];
914
+ for(let i=1;i<psbt.inputsLength;i++) {
915
+ signInputs.push(i);
916
+ }
917
+ const serializedPsbt = Buffer.from(psbt.toPSBT());
918
+ return {
919
+ psbt,
920
+ psbtHex: serializedPsbt.toString("hex"),
921
+ psbtBase64: serializedPsbt.toString("base64"),
922
+ signInputs
923
+ };
924
+ }
925
+
926
+ /**
927
+ * @inheritDoc
928
+ */
929
+ async submitPsbt(_psbt: Transaction | string): Promise<string> {
930
+ const psbt = parsePsbtTransaction(_psbt);
931
+
932
+ //Ensure not expired
933
+ if(this.expiry<Date.now()) {
934
+ throw new Error("Quote expired!");
935
+ }
936
+
937
+ //Ensure valid state
938
+ if(this._state!==SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED && this._state!==SpvFromBTCSwapState.CREATED) {
939
+ throw new Error("Invalid swap state!");
940
+ }
941
+ if(this.url==null) throw new Error("LP URL not known, cannot submit PSBT!");
942
+
943
+ //Ensure all inputs except the 1st are finalized
944
+ for(let i=1;i<psbt.inputsLength;i++) {
945
+ if(getInputType(psbt.getInput(i)).txType==="legacy")
946
+ throw new Error("Legacy (non-segwit) inputs are not allowed in the transaction!");
947
+ psbt.finalizeIdx(i);
948
+ }
949
+ const btcTx = await this.wrapper._btcRpc.parseTransaction(Buffer.from(psbt.toBytes(true)).toString("hex"));
950
+ const data = await this._contract.getWithdrawalData(btcTx);
951
+
952
+ this.logger.debug("submitPsbt(): parsed withdrawal data: ", data);
953
+
954
+ //Verify correct withdrawal data
955
+ if(
956
+ !data.isRecipient(this.recipient) ||
957
+ data.rawAmounts[0]*this.vaultTokenMultipliers[0] !== this.outputTotalSwap ||
958
+ (data.rawAmounts[1] ?? 0n)*this.vaultTokenMultipliers[1] !== this.outputTotalGas ||
959
+ data.callerFeeRate!==this.callerFeeShare ||
960
+ data.frontingFeeRate!==this.frontingFeeShare ||
961
+ data.executionFeeRate!==this.executionFeeShare ||
962
+ data.getSpentVaultUtxo()!==this.vaultUtxo ||
963
+ BigInt(data.getNewVaultBtcAmount())!==this.vaultUtxoValue ||
964
+ !data.getNewVaultScript().equals(toOutputScript(this.wrapper._options.bitcoinNetwork, this.vaultBtcAddress)) ||
965
+ data.getExecutionData()!=null
966
+ ) {
967
+ throw new Error("Invalid withdrawal tx data submitted!");
968
+ }
969
+
970
+ //Verify correct LP output
971
+ const lpOutput = psbt.getOutput(2);
972
+ if(
973
+ lpOutput.script==null ||
974
+ lpOutput.amount!==this.btcAmount ||
975
+ !toOutputScript(this.wrapper._options.bitcoinNetwork, this.btcDestinationAddress).equals(Buffer.from(lpOutput.script))
976
+ ) {
977
+ throw new Error("Invalid LP bitcoin output in transaction!");
978
+ }
979
+
980
+ //Verify vault utxo not spent yet
981
+ if(await this.wrapper._btcRpc.isSpent(this.vaultUtxo)) {
982
+ throw new Error("Vault UTXO already spent, please create new swap!");
983
+ }
984
+
985
+ //Verify tx is parsable by the contract
986
+ try {
987
+ await this._contract.checkWithdrawalTx(data);
988
+ } catch (e: any) {
989
+ throw new Error("Transaction not parsable by the contract: "+(e.message ?? e.toString()));
990
+ }
991
+
992
+ //Ensure still not expired
993
+ if(this.expiry<Date.now()) {
994
+ throw new Error("Quote expired!");
995
+ }
996
+
997
+ this._data = data;
998
+ this.initiated = true;
999
+ this.posted = true;
1000
+ await this._saveAndEmit(SpvFromBTCSwapState.SIGNED);
1001
+
1002
+ try {
1003
+ await IntermediaryAPI.initSpvFromBTC(
1004
+ this.chainIdentifier,
1005
+ this.url,
1006
+ {
1007
+ quoteId: this.quoteId,
1008
+ psbtHex: Buffer.from(psbt.toPSBT(0)).toString("hex")
1009
+ }
1010
+ );
1011
+ await this._saveAndEmit(SpvFromBTCSwapState.POSTED);
1012
+ } catch (e) {
1013
+ await this._saveAndEmit(SpvFromBTCSwapState.DECLINED);
1014
+ throw e;
1015
+ }
1016
+
1017
+ return this._data.getTxId();
1018
+ }
1019
+
1020
+ /**
1021
+ * @inheritDoc
1022
+ */
1023
+ async estimateBitcoinFee(_bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface, feeRate?: number): Promise<TokenAmount<BtcToken<false>, true> | null> {
1024
+ const bitcoinWallet: IBitcoinWallet = toBitcoinWallet(_bitcoinWallet, this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork);
1025
+ const txFee = await bitcoinWallet.getFundedPsbtFee((await this.getPsbt()).psbt, feeRate);
1026
+ if(txFee==null) return null;
1027
+ return toTokenAmount(BigInt(txFee), BitcoinTokens.BTC, this.wrapper._prices, this.pricingInfo);
1028
+ }
1029
+
1030
+ /**
1031
+ * @inheritDoc
1032
+ */
1033
+ async sendBitcoinTransaction(
1034
+ wallet: IBitcoinWallet | MinimalBitcoinWalletInterfaceWithSigner,
1035
+ feeRate?: number,
1036
+ utxos?: BitcoinWalletUtxo[],
1037
+ spendFully?: boolean
1038
+ ): Promise<string> {
1039
+ const {psbt, psbtBase64, psbtHex, signInputs} = await this.getFundedPsbt(wallet, feeRate, undefined, utxos, spendFully);
1040
+ let signedPsbt: Transaction | string;
1041
+ if(isIBitcoinWallet(wallet)) {
1042
+ signedPsbt = await wallet.signPsbt(psbt, signInputs);
1043
+ } else {
1044
+ signedPsbt = await wallet.signPsbt({
1045
+ psbt, psbtHex, psbtBase64
1046
+ }, signInputs);
1047
+ }
1048
+ return await this.submitPsbt(signedPsbt);
1049
+ }
1050
+
1051
+ /**
1052
+ * Executes the swap with the provided bitcoin wallet
1053
+ *
1054
+ * @param wallet Bitcoin wallet to use to sign the bitcoin transaction
1055
+ * @param callbacks Callbacks to track the progress of the swap
1056
+ * @param options Optional options for the swap like feeRate, AbortSignal, and timeouts/intervals
1057
+ *
1058
+ * @returns {boolean} Whether a swap was settled automatically by swap watchtowers or requires manual claim by the
1059
+ * user, in case `false` is returned the user should call the {@link claim} function to settle the swap on the
1060
+ * destination manually
1061
+ */
1062
+ async execute(
1063
+ wallet: IBitcoinWallet | MinimalBitcoinWalletInterfaceWithSigner,
1064
+ callbacks?: {
1065
+ onSourceTransactionSent?: (sourceTxId: string) => void,
1066
+ onSourceTransactionConfirmationStatus?: (sourceTxId?: string, confirmations?: number, targetConfirations?: number, etaMs?: number) => void,
1067
+ onSourceTransactionConfirmed?: (sourceTxId: string) => void,
1068
+ onSwapSettled?: (destinationTxId: string) => void
1069
+ },
1070
+ options?: {
1071
+ feeRate?: number,
1072
+ abortSignal?: AbortSignal,
1073
+ btcTxCheckIntervalSeconds?: number,
1074
+ maxWaitTillAutomaticSettlementSeconds?: number,
1075
+ utxos?: BitcoinWalletUtxo[],
1076
+ spendFully?: boolean
1077
+ }
1078
+ ): Promise<boolean> {
1079
+ if(this._state===SpvFromBTCSwapState.CLOSED) throw new Error("Swap encountered a catastrophic failure!");
1080
+ if(this._state===SpvFromBTCSwapState.FAILED) throw new Error("Swap failed!");
1081
+ if(this._state===SpvFromBTCSwapState.DECLINED) throw new Error("Swap execution already declined by the LP!");
1082
+ if(this._state===SpvFromBTCSwapState.QUOTE_EXPIRED) throw new Error("Swap quote expired!");
1083
+ if(this._state===SpvFromBTCSwapState.CLAIMED || this._state===SpvFromBTCSwapState.FRONTED) throw new Error("Swap already settled or fronted!");
1084
+
1085
+ if(this._state===SpvFromBTCSwapState.CREATED) {
1086
+ const txId = await this.sendBitcoinTransaction(wallet, options?.feeRate, options?.utxos, options?.spendFully);
1087
+ if(callbacks?.onSourceTransactionSent!=null) callbacks.onSourceTransactionSent(txId);
1088
+ }
1089
+ if(this._state===SpvFromBTCSwapState.POSTED || this._state===SpvFromBTCSwapState.BROADCASTED) {
1090
+ const txId = await this.waitForBitcoinTransaction(callbacks?.onSourceTransactionConfirmationStatus, options?.btcTxCheckIntervalSeconds, options?.abortSignal);
1091
+ if (callbacks?.onSourceTransactionConfirmed != null) callbacks.onSourceTransactionConfirmed(txId);
1092
+ }
1093
+ // @ts-ignore
1094
+ if(this._state===SpvFromBTCSwapState.CLAIMED || this._state===SpvFromBTCSwapState.FRONTED) return true;
1095
+ if(this._state===SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
1096
+ const success = await this.waitTillClaimedOrFronted(options?.maxWaitTillAutomaticSettlementSeconds ?? 60, options?.abortSignal);
1097
+ if(success && callbacks?.onSwapSettled!=null) callbacks.onSwapSettled(this.getOutputTxId()!);
1098
+ return success;
1099
+ }
1100
+
1101
+ throw new Error("Unexpected state reached!");
1102
+ }
1103
+
1104
+ /**
1105
+ * @inheritDoc
1106
+ *
1107
+ * @param options.bitcoinFeeRate Optional fee rate to use for the created Bitcoin transaction
1108
+ * @param options.bitcoinWallet Optional bitcoin wallet address specification to return a funded PSBT,
1109
+ * if not provided a raw PSBT is returned instead which necessitates the implementor to manually add
1110
+ * inputs to the bitcoin transaction and **set the nSequence field of the 2nd input** (input 1 -
1111
+ * indexing from 0) to the value returned in `in1sequence`
1112
+ */
1113
+ async txsExecute(options?: {
1114
+ bitcoinFeeRate?: number,
1115
+ bitcoinWallet?: MinimalBitcoinWalletInterface,
1116
+ }): Promise<[
1117
+ SwapExecutionActionBitcoin<"RAW_PSBT" | "FUNDED_PSBT">
1118
+ ]> {
1119
+ if(this._state===SpvFromBTCSwapState.CREATED) {
1120
+ if (!await this._verifyQuoteValid()) throw new Error("Quote already expired or close to expiry!");
1121
+ return [
1122
+ {
1123
+ name: "Payment",
1124
+ description: "Send funds to the bitcoin swap address",
1125
+ chain: "BITCOIN",
1126
+ txs: [
1127
+ options?.bitcoinWallet==null
1128
+ ? {...await this.getPsbt(), type: "RAW_PSBT"}
1129
+ : {...await this.getFundedPsbt(options.bitcoinWallet, options?.bitcoinFeeRate), type: "FUNDED_PSBT"}
1130
+ ]
1131
+ }
1132
+ ];
1133
+ }
1134
+
1135
+ throw new Error("Invalid swap state to obtain execution txns, required CREATED");
1136
+ }
1137
+
1138
+
1139
+ /**
1140
+ * @inheritDoc
1141
+ *
1142
+ * @param options.bitcoinFeeRate Optional fee rate to use for the created Bitcoin transaction
1143
+ * @param options.bitcoinWallet Optional bitcoin wallet address specification to return a funded PSBT,
1144
+ * if not provided a raw PSBT is returned instead which necessitates the implementor to manually add
1145
+ * inputs to the bitcoin transaction and **set the nSequence field of the 2nd input** (input 1 -
1146
+ * indexing from 0) to the value returned in `in1sequence`
1147
+ * @param options.manualSettlementSmartChainSigner Optional smart chain signer to create a manual claim (settlement) transaction
1148
+ * @param options.maxWaitTillAutomaticSettlementSeconds Maximum time to wait for an automatic settlement after
1149
+ * the bitcoin transaction is confirmed (defaults to 60 seconds)
1150
+ */
1151
+ async getCurrentActions(options?: {
1152
+ bitcoinFeeRate?: number,
1153
+ bitcoinWallet?: MinimalBitcoinWalletInterface,
1154
+ manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
1155
+ maxWaitTillAutomaticSettlementSeconds?: number
1156
+ }): Promise<SwapExecutionAction<T>[]> {
1157
+ if(this._state===SpvFromBTCSwapState.CREATED) {
1158
+ try {
1159
+ return await this.txsExecute(options);
1160
+ } catch (e) {}
1161
+ }
1162
+ if(this._state===SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
1163
+ if(
1164
+ this.btcTxConfirmedAt==null ||
1165
+ options?.maxWaitTillAutomaticSettlementSeconds===0 ||
1166
+ (Date.now() - this.btcTxConfirmedAt) > (options?.maxWaitTillAutomaticSettlementSeconds ?? 60)*1000
1167
+ ) {
1168
+ return [{
1169
+ name: "Claim" as const,
1170
+ description: "Manually settle (claim) the swap on the destination smart chain",
1171
+ chain: this.chainIdentifier,
1172
+ txs: await this.txsClaim(options?.manualSettlementSmartChainSigner)
1173
+ }];
1174
+ }
1175
+ }
1176
+ return [];
1177
+ }
1178
+
1179
+
1180
+ //////////////////////////////
1181
+ //// Bitcoin tx listener
1182
+
1183
+ /**
1184
+ * Checks whether a bitcoin payment was already made, returns the payment or null when no payment has been made.
1185
+ * @internal
1186
+ */
1187
+ protected async getBitcoinPayment(): Promise<{
1188
+ txId: string,
1189
+ confirmations: number,
1190
+ targetConfirmations: number,
1191
+ inputAddresses?: string[]
1192
+ } | null> {
1193
+ if(this._data?.btcTx?.txid==null) return null;
1194
+
1195
+ const result = await this.wrapper._btcRpc.getTransaction(this._data?.btcTx?.txid);
1196
+ if(result==null) return null;
1197
+
1198
+ return {
1199
+ txId: result.txid,
1200
+ confirmations: result.confirmations ?? 0,
1201
+ targetConfirmations: this.vaultRequiredConfirmations,
1202
+ inputAddresses: result.inputAddresses
1203
+ }
1204
+ }
1205
+
1206
+ /**
1207
+ * @inheritDoc
1208
+ *
1209
+ * @throws {Error} if in invalid state (must be {@link SpvFromBTCSwapState.POSTED} or
1210
+ * {@link SpvFromBTCSwapState.BROADCASTED} states)
1211
+ */
1212
+ async waitForBitcoinTransaction(
1213
+ updateCallback?: (txId?: string, confirmations?: number, targetConfirmations?: number, txEtaMs?: number) => void,
1214
+ checkIntervalSeconds?: number,
1215
+ abortSignal?: AbortSignal
1216
+ ): Promise<string> {
1217
+ if(
1218
+ this._state!==SpvFromBTCSwapState.POSTED &&
1219
+ this._state!==SpvFromBTCSwapState.BROADCASTED &&
1220
+ !(this._state===SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED && this.posted)
1221
+ ) throw new Error("Must be in POSTED or BROADCASTED state!");
1222
+ if(this._data==null) throw new Error("Expected swap to have withdrawal data filled!");
1223
+
1224
+ const result = await this.wrapper._btcRpc.waitForTransaction(
1225
+ this._data.btcTx.txid,
1226
+ this.vaultRequiredConfirmations,
1227
+ (btcTx?: BtcTxWithBlockheight, txEtaMs?: number) => {
1228
+ if(updateCallback!=null) updateCallback(btcTx?.txid, btcTx?.confirmations, this.vaultRequiredConfirmations, txEtaMs);
1229
+ if(btcTx==null) return;
1230
+ let save = false;
1231
+ if(btcTx.inputAddresses!=null && this._senderAddress==null) {
1232
+ this._senderAddress = btcTx.inputAddresses[1];
1233
+ save = true;
1234
+ }
1235
+ if(this._state===SpvFromBTCSwapState.POSTED || this._state==SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED) {
1236
+ this._state = SpvFromBTCSwapState.BROADCASTED;
1237
+ save = true;
1238
+ }
1239
+ if(save) this._saveAndEmit();
1240
+ },
1241
+ abortSignal,
1242
+ checkIntervalSeconds
1243
+ );
1244
+
1245
+ if(abortSignal!=null) abortSignal.throwIfAborted();
1246
+
1247
+ let save = false;
1248
+ if(result.inputAddresses!=null && this._senderAddress==null) {
1249
+ this._senderAddress = result.inputAddresses[1];
1250
+ save = true;
1251
+ }
1252
+ if(
1253
+ (this._state as SpvFromBTCSwapState)!==SpvFromBTCSwapState.FRONTED &&
1254
+ (this._state as SpvFromBTCSwapState)!==SpvFromBTCSwapState.CLAIMED
1255
+ ) {
1256
+ this.btcTxConfirmedAt ??= Date.now();
1257
+ this._state = SpvFromBTCSwapState.BTC_TX_CONFIRMED;
1258
+ save = true;
1259
+ }
1260
+ if(save) await this._saveAndEmit();
1261
+
1262
+ return result.txid;
1263
+ }
1264
+
1265
+
1266
+ //////////////////////////////
1267
+ //// Claim
1268
+
1269
+ /**
1270
+ * Returns transactions for settling (claiming) the swap if the swap requires manual settlement, you can check so
1271
+ * with isClaimable. After sending the transaction manually be sure to call the waitTillClaimed function to wait
1272
+ * till the claim transaction is observed, processed by the SDK and state of the swap properly updated.
1273
+ *
1274
+ * @remarks
1275
+ * Might also return transactions necessary to sync the bitcoin light client.
1276
+ *
1277
+ * @param _signer Address of the signer to create the claim transactions for, can also be different to the recipient
1278
+ *
1279
+ * @throws {Error} If the swap is in invalid state (must be {@link SpvFromBTCSwapState.BTC_TX_CONFIRMED})
1280
+ */
1281
+ async txsClaim(_signer?: string | T["Signer"] | T["NativeSigner"]): Promise<T["TX"][]> {
1282
+ let address: string | undefined = undefined;
1283
+ if(_signer!=null) {
1284
+ if (typeof (_signer) === "string") {
1285
+ address = _signer;
1286
+ } else if (isAbstractSigner(_signer)) {
1287
+ address = _signer.getAddress();
1288
+ } else {
1289
+ address = (await this.wrapper._chain.wrapSigner(_signer)).getAddress();
1290
+ }
1291
+ }
1292
+
1293
+ if(!this.isClaimable()) throw new Error("Must be in BTC_TX_CONFIRMED state!");
1294
+ if(this._data==null) throw new Error("Expected swap to have withdrawal data filled!");
1295
+
1296
+ const vaultData = await this._contract.getVaultData(this.vaultOwner, this.vaultId);
1297
+ if(vaultData==null) throw new Error(`Vault data for ${this.vaultOwner}:${this.vaultId.toString(10)} not found (already closed???)!`);
1298
+
1299
+ const btcTx = await this.wrapper._btcRpc.getTransaction(this._data.btcTx.txid);
1300
+ if(btcTx==null) throw new Error(`Bitcoin transaction ${this._data.btcTx.txid} not found!`);
1301
+ const txs = [btcTx];
1302
+
1303
+ //Trace back from current tx to the vaultData-specified UTXO
1304
+ const vaultUtxo = vaultData.getUtxo();
1305
+ while(txs[0].ins[0].txid+":"+txs[0].ins[0].vout!==vaultUtxo) {
1306
+ const btcTx = await this.wrapper._btcRpc.getTransaction(txs[0].ins[0].txid);
1307
+ if(btcTx==null) throw new Error(`Prior withdrawal bitcoin transaction ${this._data.btcTx.txid} not found!`);
1308
+ txs.unshift(btcTx);
1309
+ }
1310
+
1311
+ //Parse transactions to withdrawal data
1312
+ const withdrawalData: T["SpvVaultWithdrawalData"][] = [];
1313
+ for(let tx of txs) {
1314
+ withdrawalData.push(await this._contract.getWithdrawalData(tx));
1315
+ }
1316
+
1317
+ return await this._contract.txsClaim(
1318
+ address ?? this._getInitiator(), vaultData,
1319
+ withdrawalData.map(tx => {return {tx}}),
1320
+ this.wrapper._synchronizer(this._contractVersion), true
1321
+ );
1322
+ }
1323
+
1324
+ /**
1325
+ * Settles the swap by claiming the funds on the destination chain if the swap requires manual settlement, you can
1326
+ * check so with isClaimable.
1327
+ *
1328
+ * @remarks
1329
+ * Might also sync the bitcoin light client during the process.
1330
+ *
1331
+ * @param _signer Signer to use for signing the settlement transactions, can also be different to the recipient
1332
+ * @param abortSignal Abort signal
1333
+ * @param onBeforeTxSent Optional callback triggered before the claim transaction is broadcasted
1334
+ *
1335
+ * @throws {Error} If the swap is in invalid state (must be {@link SpvFromBTCSwapState.BTC_TX_CONFIRMED})
1336
+ */
1337
+ async claim(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, onBeforeTxSent?: (txId: string) => void): Promise<string> {
1338
+ const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
1339
+ let txIds: string[];
1340
+ try {
1341
+ let txCount = 0;
1342
+ const txs = await this.txsClaim(signer);
1343
+ txIds = await this.wrapper._chain.sendAndConfirm(
1344
+ signer, txs, true, abortSignal, undefined, (txId: string) => {
1345
+ txCount++;
1346
+ if(onBeforeTxSent!=null && txCount===txs.length) onBeforeTxSent(txId);
1347
+ return Promise.resolve();
1348
+ }
1349
+ );
1350
+ } catch (e) {
1351
+ if(this._data==null) throw e;
1352
+
1353
+ this.logger.info("claim(): Failed to claim ourselves, checking swap claim state...");
1354
+ if(this._state===SpvFromBTCSwapState.CLAIMED) {
1355
+ this.logger.info("claim(): Transaction state is CLAIMED, swap was successfully claimed by the watchtower");
1356
+ return this._claimTxId!;
1357
+ }
1358
+ const withdrawalState = await this._contract.getWithdrawalState(this._data, this._genesisSmartChainBlockHeight);
1359
+ if(withdrawalState!=null && withdrawalState.type===SpvWithdrawalStateType.CLAIMED) {
1360
+ this.logger.info("claim(): Transaction status is CLAIMED, swap was successfully claimed by the watchtower");
1361
+ this._claimTxId = withdrawalState.txId;
1362
+ await this._saveAndEmit(SpvFromBTCSwapState.CLAIMED);
1363
+ return withdrawalState.txId;
1364
+ }
1365
+ throw e;
1366
+ }
1367
+
1368
+ this._claimTxId = txIds[0];
1369
+ if(
1370
+ this._state===SpvFromBTCSwapState.POSTED || this._state===SpvFromBTCSwapState.BROADCASTED ||
1371
+ this._state===SpvFromBTCSwapState.BTC_TX_CONFIRMED || this._state===SpvFromBTCSwapState.FAILED ||
1372
+ this._state===SpvFromBTCSwapState.FRONTED
1373
+ ) {
1374
+ await this._saveAndEmit(SpvFromBTCSwapState.CLAIMED);
1375
+ }
1376
+ return txIds[0];
1377
+ }
1378
+
1379
+ /**
1380
+ * Periodically checks the chain to see whether the swap was finished (claimed or refunded)
1381
+ *
1382
+ * @param abortSignal
1383
+ * @param interval How often to check (in seconds), default to 5s
1384
+ * @internal
1385
+ */
1386
+ protected async watchdogWaitTillResult(abortSignal?: AbortSignal, interval: number = 5): Promise<
1387
+ SpvWithdrawalClaimedState | SpvWithdrawalFrontedState | SpvWithdrawalClosedState
1388
+ > {
1389
+ if(this._data==null) throw new Error("Cannot await the result before the btc transaction is sent!");
1390
+
1391
+ let status: SpvWithdrawalState = {type: SpvWithdrawalStateType.NOT_FOUND};
1392
+ while(status.type===SpvWithdrawalStateType.NOT_FOUND) {
1393
+ await timeoutPromise(interval*1000, abortSignal);
1394
+ try {
1395
+ //Be smart about checking withdrawal state
1396
+ if(await this._shouldCheckWithdrawalState()) {
1397
+ status = await this._contract.getWithdrawalState(
1398
+ this._data, this._genesisSmartChainBlockHeight
1399
+ ) ?? {type: SpvWithdrawalStateType.NOT_FOUND};
1400
+ }
1401
+ } catch (e) {
1402
+ this.logger.error("watchdogWaitTillResult(): Error when fetching commit status: ", e);
1403
+ }
1404
+ }
1405
+ if(abortSignal!=null) abortSignal.throwIfAborted();
1406
+ return status;
1407
+ }
1408
+
1409
+ /**
1410
+ * Waits till the swap is successfully settled (claimed), should be called after sending the claim (settlement)
1411
+ * transactions manually to wait till the SDK processes the settlement and updates the swap state accordingly.
1412
+ *
1413
+ * @remarks
1414
+ * This is an alias for the {@link waitTillClaimedOrFronted} function and will also resolve if the swap has
1415
+ * been fronted (not necessarily claimed)
1416
+ *
1417
+ * @param maxWaitTimeSeconds – Maximum time in seconds to wait for the swap to be settled
1418
+ * @param abortSignal AbortSignal
1419
+ *
1420
+ * @returns Whether the swap was claimed in time or not
1421
+ */
1422
+ waitTillClaimed(maxWaitTimeSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
1423
+ return this.waitTillClaimedOrFronted(maxWaitTimeSeconds, abortSignal);
1424
+ }
1425
+
1426
+ /**
1427
+ * Waits till the swap is successfully fronted or settled on the destination chain
1428
+ *
1429
+ * @param maxWaitTimeSeconds Maximum time in seconds to wait for the swap to be settled (by default
1430
+ * it waits indefinitely)
1431
+ * @param abortSignal Abort signal
1432
+ *
1433
+ * @returns {boolean} whether the swap was claimed or fronted automatically or not, if the swap was not claimed
1434
+ * the user can claim manually through the {@link claim} function
1435
+ */
1436
+ async waitTillClaimedOrFronted(maxWaitTimeSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
1437
+ if(this._state===SpvFromBTCSwapState.CLAIMED || this._state===SpvFromBTCSwapState.FRONTED) return Promise.resolve(true);
1438
+
1439
+ const abortController = extendAbortController(abortSignal);
1440
+
1441
+ let timedOut: boolean = false;
1442
+ if(maxWaitTimeSeconds!=null) {
1443
+ const timeout = setTimeout(() => {
1444
+ timedOut = true;
1445
+ abortController.abort();
1446
+ }, maxWaitTimeSeconds * 1000);
1447
+ abortController.signal.addEventListener("abort", () => clearTimeout(timeout));
1448
+ }
1449
+
1450
+ let res: number | SpvWithdrawalState;
1451
+ try {
1452
+ res = await Promise.race([
1453
+ this.watchdogWaitTillResult(abortController.signal),
1454
+ this.waitTillState(SpvFromBTCSwapState.CLAIMED, "eq", abortController.signal).then(() => 0),
1455
+ this.waitTillState(SpvFromBTCSwapState.FRONTED, "eq", abortController.signal).then(() => 1),
1456
+ this.waitTillState(SpvFromBTCSwapState.FAILED, "eq", abortController.signal).then(() => 2),
1457
+ ]);
1458
+ abortController.abort();
1459
+ } catch (e) {
1460
+ abortController.abort();
1461
+ if(timedOut) return false;
1462
+ throw e;
1463
+ }
1464
+
1465
+ if(typeof(res)==="number") {
1466
+ if(res===0) {
1467
+ this.logger.debug("waitTillClaimedOrFronted(): Resolved from state change (CLAIMED)");
1468
+ return true;
1469
+ }
1470
+ if(res===1) {
1471
+ this.logger.debug("waitTillClaimedOrFronted(): Resolved from state change (FRONTED)");
1472
+ return true;
1473
+ }
1474
+ if(res===2) {
1475
+ this.logger.debug("waitTillClaimedOrFronted(): Resolved from state change (FAILED)");
1476
+ throw new Error("Swap failed while waiting for claim or front");
1477
+ }
1478
+ throw new Error("Invalid numeric response, this should never happen!");
1479
+ }
1480
+ this.logger.debug("waitTillClaimedOrFronted(): Resolved from watchdog");
1481
+
1482
+ if(res.type===SpvWithdrawalStateType.FRONTED) {
1483
+ if(
1484
+ (this._state as SpvFromBTCSwapState)!==SpvFromBTCSwapState.FRONTED ||
1485
+ (this._state as SpvFromBTCSwapState)!==SpvFromBTCSwapState.CLAIMED
1486
+ ) {
1487
+ this._frontTxId = res.txId;
1488
+ await this._saveAndEmit(SpvFromBTCSwapState.FRONTED);
1489
+ }
1490
+ }
1491
+ if(res.type===SpvWithdrawalStateType.CLAIMED) {
1492
+ if(
1493
+ (this._state as SpvFromBTCSwapState)!==SpvFromBTCSwapState.CLAIMED
1494
+ ) {
1495
+ this._claimTxId = res.txId;
1496
+ await this._saveAndEmit(SpvFromBTCSwapState.CLAIMED);
1497
+ }
1498
+ }
1499
+ if(res.type===SpvWithdrawalStateType.CLOSED) {
1500
+ if(
1501
+ (this._state as SpvFromBTCSwapState)!==SpvFromBTCSwapState.CLOSED
1502
+ ) await this._saveAndEmit(SpvFromBTCSwapState.CLOSED);
1503
+ throw new Error("Swap failed with catastrophic error!");
1504
+ }
1505
+
1506
+ return true;
1507
+ }
1508
+
1509
+ /**
1510
+ * Waits till the bitcoin transaction confirms and swap settled on the destination chain
1511
+ *
1512
+ * @param updateCallback Callback called when txId is found, and also called with subsequent confirmations
1513
+ * @param checkIntervalSeconds How often to check the bitcoin transaction (5 seconds by default)
1514
+ * @param abortSignal Abort signal
1515
+ *
1516
+ * @throws {Error} if in invalid state (must be {@link SpvFromBTCSwapState.POSTED} or
1517
+ * {@link SpvFromBTCSwapState.BROADCASTED} states)
1518
+ */
1519
+ async waitTillExecuted(
1520
+ updateCallback?: (txId?: string, confirmations?: number, targetConfirmations?: number, txEtaMs?: number) => void,
1521
+ checkIntervalSeconds?: number,
1522
+ abortSignal?: AbortSignal
1523
+ ): Promise<void> {
1524
+ await this.waitForBitcoinTransaction(updateCallback, checkIntervalSeconds, abortSignal);
1525
+ await this.waitTillClaimedOrFronted(undefined, abortSignal);
1526
+ }
1527
+
1528
+
1529
+ //////////////////////////////
1530
+ //// Storage
1531
+
1532
+ /**
1533
+ * @inheritDoc
1534
+ */
1535
+ serialize(): any {
1536
+ return {
1537
+ ...super.serialize(),
1538
+ quoteId: this.quoteId,
1539
+ recipient: this.recipient,
1540
+ vaultOwner: this.vaultOwner,
1541
+ vaultId: this.vaultId.toString(10),
1542
+ vaultRequiredConfirmations: this.vaultRequiredConfirmations,
1543
+ vaultTokenMultipliers: this.vaultTokenMultipliers.map(val => val.toString(10)),
1544
+ vaultBtcAddress: this.vaultBtcAddress,
1545
+ vaultUtxo: this.vaultUtxo,
1546
+ vaultUtxoValue: this.vaultUtxoValue.toString(10),
1547
+ btcDestinationAddress: this.btcDestinationAddress,
1548
+ btcAmount: this.btcAmount.toString(10),
1549
+ btcAmountSwap: this.btcAmountSwap.toString(10),
1550
+ btcAmountGas: this.btcAmountGas.toString(10),
1551
+ minimumBtcFeeRate: this.minimumBtcFeeRate,
1552
+ outputTotalSwap: this.outputTotalSwap.toString(10),
1553
+ outputSwapToken: this.outputSwapToken,
1554
+ outputTotalGas: this.outputTotalGas.toString(10),
1555
+ outputGasToken: this.outputGasToken,
1556
+ gasSwapFeeBtc: this.gasSwapFeeBtc.toString(10),
1557
+ gasSwapFee: this.gasSwapFee.toString(10),
1558
+ callerFeeShare: this.callerFeeShare.toString(10),
1559
+ frontingFeeShare: this.frontingFeeShare.toString(10),
1560
+ executionFeeShare: this.executionFeeShare.toString(10),
1561
+ genesisSmartChainBlockHeight: this._genesisSmartChainBlockHeight,
1562
+ gasPricingInfo: serializePriceInfoType(this.gasPricingInfo),
1563
+ posted: this.posted,
1564
+
1565
+ senderAddress: this._senderAddress,
1566
+ claimTxId: this._claimTxId,
1567
+ frontTxId: this._frontTxId,
1568
+ data: this._data?.serialize(),
1569
+ btcTxConfirmedAt: this.btcTxConfirmedAt
1570
+ };
1571
+ }
1572
+
1573
+
1574
+ //////////////////////////////
1575
+ //// Swap ticks & sync
1576
+
1577
+ /**
1578
+ * Used to set the txId of the bitcoin payment from the on-chain events listener
1579
+ *
1580
+ * @param txId
1581
+ * @internal
1582
+ */
1583
+ async _setBitcoinTxId(txId: string) {
1584
+ if(this._data==null) return;
1585
+ if(txId!=this._data.btcTx.txid) return;
1586
+
1587
+ if(this._senderAddress!=null) return;
1588
+ const btcTx = await this.wrapper._btcRpc.getTransaction(txId);
1589
+ if(btcTx==null || btcTx.inputAddresses==null) return;
1590
+
1591
+ this._senderAddress = btcTx.inputAddresses[1];
1592
+ }
1593
+
1594
+ private btcTxLastChecked?: number;
1595
+
1596
+ /**
1597
+ * @internal
1598
+ */
1599
+ async _syncStateFromBitcoin(save?: boolean) {
1600
+ if(this._data?.btcTx==null) return false;
1601
+
1602
+ //Check if bitcoin payment was confirmed
1603
+ this.btcTxLastChecked = Date.now();
1604
+ const res = await this.getBitcoinPayment();
1605
+ if(res==null) {
1606
+ //Check inputs double-spent
1607
+ for(let input of this._data.btcTx.ins) {
1608
+ if(await this.wrapper._btcRpc.isSpent(input.txid+":"+input.vout, true)) {
1609
+ if(
1610
+ this._state===SpvFromBTCSwapState.SIGNED ||
1611
+ this._state===SpvFromBTCSwapState.POSTED ||
1612
+ this._state===SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED ||
1613
+ this._state===SpvFromBTCSwapState.DECLINED
1614
+ ) {
1615
+ //One of the inputs was double-spent
1616
+ this._state = SpvFromBTCSwapState.QUOTE_EXPIRED;
1617
+ } else {
1618
+ //One of the inputs was double-spent
1619
+ this._state = SpvFromBTCSwapState.FAILED;
1620
+ }
1621
+ if(save) await this._saveAndEmit();
1622
+ return true;
1623
+ }
1624
+ }
1625
+ } else {
1626
+ let needsSave = false;
1627
+ if(res.inputAddresses!=null && this._senderAddress==null) {
1628
+ this._senderAddress = res.inputAddresses[1];
1629
+ needsSave = true;
1630
+ }
1631
+ if(res.confirmations>=this.vaultRequiredConfirmations) {
1632
+ if(
1633
+ this._state!==SpvFromBTCSwapState.BTC_TX_CONFIRMED &&
1634
+ this._state!==SpvFromBTCSwapState.FRONTED &&
1635
+ this._state!==SpvFromBTCSwapState.CLAIMED
1636
+ ) {
1637
+ this.btcTxConfirmedAt ??= Date.now();
1638
+ this._state = SpvFromBTCSwapState.BTC_TX_CONFIRMED;
1639
+ needsSave = true;
1640
+ }
1641
+ } else if(
1642
+ this._state===SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED ||
1643
+ this._state===SpvFromBTCSwapState.POSTED ||
1644
+ this._state===SpvFromBTCSwapState.SIGNED ||
1645
+ this._state===SpvFromBTCSwapState.DECLINED
1646
+ ) {
1647
+ this._state = SpvFromBTCSwapState.BROADCASTED;
1648
+ needsSave = true;
1649
+ }
1650
+ if(needsSave && save) await this._saveAndEmit();
1651
+ return needsSave;
1652
+ }
1653
+ return false;
1654
+ }
1655
+
1656
+ /**
1657
+ * Checks the swap's state on-chain and compares it to its internal state, updates/changes it according to on-chain
1658
+ * data
1659
+ */
1660
+ private async syncStateFromChain(): Promise<boolean> {
1661
+ let changed: boolean = false;
1662
+
1663
+ if(
1664
+ this._state===SpvFromBTCSwapState.SIGNED ||
1665
+ this._state===SpvFromBTCSwapState.POSTED ||
1666
+ this._state===SpvFromBTCSwapState.BROADCASTED ||
1667
+ this._state===SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED ||
1668
+ this._state===SpvFromBTCSwapState.DECLINED ||
1669
+ this._state===SpvFromBTCSwapState.BTC_TX_CONFIRMED
1670
+ ) {
1671
+ //Check BTC transaction
1672
+ if(await this._syncStateFromBitcoin(false)) changed ||= true;
1673
+ }
1674
+
1675
+ if(this._state===SpvFromBTCSwapState.BROADCASTED || this._state===SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
1676
+ if(await this._shouldCheckWithdrawalState()) {
1677
+ const status = await this._contract.getWithdrawalState(this._data!, this._genesisSmartChainBlockHeight);
1678
+ this.logger.debug("syncStateFromChain(): status of "+this._data!.btcTx.txid, status);
1679
+ switch(status?.type) {
1680
+ case SpvWithdrawalStateType.FRONTED:
1681
+ this._frontTxId = status.txId;
1682
+ this._state = SpvFromBTCSwapState.FRONTED;
1683
+ changed ||= true;
1684
+ break;
1685
+ case SpvWithdrawalStateType.CLAIMED:
1686
+ this._claimTxId = status.txId;
1687
+ this._state = SpvFromBTCSwapState.CLAIMED;
1688
+ changed ||= true;
1689
+ break;
1690
+ case SpvWithdrawalStateType.CLOSED:
1691
+ this._state = SpvFromBTCSwapState.CLOSED;
1692
+ changed ||= true;
1693
+ break;
1694
+ }
1695
+ }
1696
+ }
1697
+
1698
+ if(
1699
+ this._state===SpvFromBTCSwapState.CREATED ||
1700
+ this._state===SpvFromBTCSwapState.SIGNED ||
1701
+ this._state===SpvFromBTCSwapState.POSTED
1702
+ ) {
1703
+ if(this.expiry<Date.now()) {
1704
+ if(this._state===SpvFromBTCSwapState.CREATED) {
1705
+ this._state = SpvFromBTCSwapState.QUOTE_EXPIRED;
1706
+ } else {
1707
+ this._state = SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED;
1708
+ }
1709
+ changed ||= true;
1710
+ }
1711
+ }
1712
+
1713
+ return changed;
1714
+ }
1715
+
1716
+ /**
1717
+ * @inheritDoc
1718
+ * @internal
1719
+ */
1720
+ async _sync(save?: boolean): Promise<boolean> {
1721
+ const changed = await this.syncStateFromChain();
1722
+ if(changed && save) await this._saveAndEmit();
1723
+ return changed;
1724
+ }
1725
+
1726
+ /**
1727
+ * @inheritDoc
1728
+ * @internal
1729
+ */
1730
+ async _tick(save?: boolean): Promise<boolean> {
1731
+ if(
1732
+ this._state===SpvFromBTCSwapState.CREATED ||
1733
+ this._state===SpvFromBTCSwapState.SIGNED
1734
+ ) {
1735
+ if(this.getQuoteExpiry()<Date.now()) {
1736
+ this._state = SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED;
1737
+ if(save) await this._saveAndEmit();
1738
+ return true;
1739
+ }
1740
+ }
1741
+
1742
+ if(this._state===SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED && !this.posted) {
1743
+ if(this.expiry<Date.now()) {
1744
+ this._state = SpvFromBTCSwapState.QUOTE_EXPIRED;
1745
+ if(save) await this._saveAndEmit();
1746
+ return true;
1747
+ }
1748
+ }
1749
+
1750
+ if(this.btcTxLastChecked==null || Date.now() - this.btcTxLastChecked > 120_000) {
1751
+ if (
1752
+ this._state === SpvFromBTCSwapState.POSTED ||
1753
+ this._state === SpvFromBTCSwapState.BROADCASTED
1754
+ ) {
1755
+ try {
1756
+ //Check if bitcoin payment was confirmed
1757
+ return await this._syncStateFromBitcoin(save);
1758
+ } catch (e) {
1759
+ this.logger.error("tickSwap("+this.getId()+"): ", e);
1760
+ }
1761
+ }
1762
+ }
1763
+
1764
+ return false;
1765
+ }
1766
+
1767
+ /**
1768
+ * Checks whether an on-chain withdrawal state should be fetched for this specific swap
1769
+ *
1770
+ * @internal
1771
+ */
1772
+ async _shouldCheckWithdrawalState(frontingAddress?: string | null, vaultDataUtxo?: string | null) {
1773
+ if(frontingAddress===undefined) frontingAddress = await this._contract.getFronterAddress(this.vaultOwner, this.vaultId, this._data!);
1774
+ if(vaultDataUtxo===undefined) vaultDataUtxo = await this._contract.getVaultLatestUtxo(this.vaultOwner, this.vaultId);
1775
+
1776
+ if(frontingAddress != null) return true; //In case the swap is fronted there will for sure be a fronted event
1777
+ if(vaultDataUtxo == null) return true; //Vault UTXO is null (the vault closed)
1778
+
1779
+ const [txId, _] = vaultDataUtxo.split(":");
1780
+ //Don't check both txns if their txId is equal
1781
+ if(this._data!.btcTx.txid===txId) return true;
1782
+ const [btcTx, latestVaultTx] = await Promise.all([
1783
+ this.wrapper._btcRpc.getTransaction(this._data!.btcTx.txid),
1784
+ this.wrapper._btcRpc.getTransaction(txId)
1785
+ ]);
1786
+
1787
+ if(latestVaultTx==null || latestVaultTx.blockheight==null) {
1788
+ //Something must've gone horribly wrong, the latest vault utxo tx of the vault either
1789
+ // cannot be found on bitcoin network or is not even confirmed yet
1790
+ this.logger.debug(`_shouldCheckWithdrawalState(): Latest vault utxo not found or not confirmed on bitcoin ${txId}`);
1791
+ return false;
1792
+ }
1793
+
1794
+ if(btcTx!=null) {
1795
+ const btcTxHeight = btcTx.blockheight;
1796
+ const latestVaultTxHeight = latestVaultTx.blockheight;
1797
+ //We also need to cover the case where bitcoin tx isn't confirmed yet (hence btxTxHeight==null)
1798
+ if(btcTxHeight==null || latestVaultTxHeight < btcTxHeight) {
1799
+ //Definitely not claimed!
1800
+ this.logger.debug(`_shouldCheckWithdrawalState(): Skipped checking withdrawal state, latestVaultTxHeight: ${latestVaultTx.blockheight}, btcTxHeight: ${btcTxHeight} and not fronted!`);
1801
+ return false;
1802
+ }
1803
+ } else {
1804
+ //Definitely not claimed because the transaction was probably double-spent (or evicted from mempool)
1805
+ this.logger.debug(`_shouldCheckWithdrawalState(): Skipped checking withdrawal state, btc tx probably replaced or evicted: ${this._data!.btcTx.txid} and not fronted`);
1806
+ return false;
1807
+ }
1808
+
1809
+ return true;
1810
+ }
1811
+
1812
+ }