@atomiqlabs/sdk 8.9.1 → 8.9.3

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