@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,947 +1,947 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SpvFromBTCWrapper = exports.REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE = exports.REQUIRED_SPV_SWAP_VAULT_ADDRESS_TYPE = void 0;
4
- const ISwapWrapper_1 = require("../ISwapWrapper");
5
- const base_1 = require("@atomiqlabs/base");
6
- const SpvFromBTCSwap_1 = require("./SpvFromBTCSwap");
7
- const utils_1 = require("@scure/btc-signer/utils");
8
- const SwapType_1 = require("../../enums/SwapType");
9
- const Intermediary_1 = require("../../intermediaries/Intermediary");
10
- const Utils_1 = require("../../utils/Utils");
11
- const BitcoinUtils_1 = require("../../utils/BitcoinUtils");
12
- const RequestError_1 = require("../../errors/RequestError");
13
- const IntermediaryError_1 = require("../../errors/IntermediaryError");
14
- const btc_signer_1 = require("@scure/btc-signer");
15
- const RetryUtils_1 = require("../../utils/RetryUtils");
16
- const UserError_1 = require("../../errors/UserError");
17
- const utils_2 = require("../../bitcoin/coinselect2/utils");
18
- const BitcoinWallet_1 = require("../../bitcoin/wallet/BitcoinWallet");
19
- exports.REQUIRED_SPV_SWAP_VAULT_ADDRESS_TYPE = "p2tr";
20
- exports.REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE = "p2wpkh";
21
- /**
22
- * New spv vault (UTXO-controlled vault) based swaps for Bitcoin -> Smart chain swaps not requiring
23
- * any initiation on the destination chain, and with the added possibility for the user to receive
24
- * a native token on the destination chain as part of the swap (a "gas drop" feature).
25
- *
26
- * @category Swaps/Bitcoin → Smart chain
27
- */
28
- class SpvFromBTCWrapper extends ISwapWrapper_1.ISwapWrapper {
29
- /**
30
- * @param chainIdentifier
31
- * @param unifiedStorage Storage interface for the current environment
32
- * @param unifiedChainEvents On-chain event listener
33
- * @param chain
34
- * @param prices Pricing to use
35
- * @param tokens
36
- * @param versionedContracts
37
- * @param versionedSynchronizer
38
- * @param btcRpc Bitcoin RPC which also supports getting transactions by txoHash
39
- * @param lpApi
40
- * @param options
41
- * @param events Instance to use for emitting events
42
- */
43
- constructor(chainIdentifier, unifiedStorage, unifiedChainEvents, chain, prices, tokens, versionedContracts, versionedSynchronizer, btcRpc, lpApi, options, events) {
44
- super(chainIdentifier, unifiedStorage, unifiedChainEvents, chain, prices, tokens, lpApi, {
45
- ...options,
46
- bitcoinNetwork: options?.bitcoinNetwork ?? utils_1.TEST_NETWORK,
47
- maxConfirmations: options?.maxConfirmations ?? 6,
48
- bitcoinBlocktime: options?.bitcoinBlocktime ?? 10 * 60,
49
- maxTransactionsDelta: options?.maxTransactionsDelta ?? 3,
50
- maxRawAmountAdjustmentDifferencePPM: options?.maxRawAmountAdjustmentDifferencePPM ?? 100,
51
- maxBtcFeeOffset: options?.maxBtcFeeOffset ?? 10,
52
- maxBtcFeeMultiplier: options?.maxBtcFeeMultiplier ?? 1.5
53
- }, events);
54
- this.TYPE = SwapType_1.SwapType.SPV_VAULT_FROM_BTC;
55
- /**
56
- * @internal
57
- */
58
- this._claimableSwapStates = [SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED];
59
- /**
60
- * @internal
61
- */
62
- this._swapDeserializer = SpvFromBTCSwap_1.SpvFromBTCSwap;
63
- /**
64
- * @internal
65
- */
66
- this.btcRelay = (version) => {
67
- const _version = version ?? "v1";
68
- const data = this.versionedContracts[_version];
69
- if (data == null)
70
- throw new Error(`Invalid contract version ${_version} requested`);
71
- return data.btcRelay;
72
- };
73
- /**
74
- * @internal
75
- */
76
- this.tickSwapState = [
77
- SpvFromBTCSwap_1.SpvFromBTCSwapState.CREATED,
78
- SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED,
79
- SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED,
80
- SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED,
81
- SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED
82
- ];
83
- /**
84
- * @internal
85
- */
86
- this._synchronizer = (version) => {
87
- const _version = version ?? "v1";
88
- const data = this.versionedSynchronizer[_version];
89
- if (data == null)
90
- throw new Error(`Invalid contract version ${_version} requested`);
91
- return data.synchronizer;
92
- };
93
- /**
94
- * @internal
95
- */
96
- this._contract = (version) => {
97
- const _version = version ?? "v1";
98
- const data = this.versionedContracts[_version];
99
- if (data == null)
100
- throw new Error(`Invalid contract version ${_version} requested`);
101
- return data.spvVaultContract;
102
- };
103
- /**
104
- * @internal
105
- */
106
- this._spvWithdrawalDataDeserializer = (version) => {
107
- const _version = version ?? "v1";
108
- const data = this.versionedContracts[_version];
109
- if (data == null)
110
- throw new Error(`Invalid contract version ${_version} requested`);
111
- return data.spvVaultWithdrawalDataConstructor;
112
- };
113
- /**
114
- * @internal
115
- */
116
- this._pendingSwapStates = [
117
- SpvFromBTCSwap_1.SpvFromBTCSwapState.CREATED,
118
- SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED,
119
- SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED,
120
- SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED,
121
- SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED,
122
- SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED,
123
- SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED
124
- ];
125
- this.versionedContracts = {};
126
- this.versionedSynchronizer = {};
127
- this.versionedContracts = versionedContracts;
128
- this.versionedSynchronizer = versionedSynchronizer;
129
- this._btcRpc = btcRpc;
130
- }
131
- async processEventFront(event, swap) {
132
- if (swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED ||
133
- swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED ||
134
- swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
135
- swap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.FRONTED;
136
- await swap._setBitcoinTxId(event.btcTxId).catch(e => {
137
- this.logger.warn("processEventFront(): Failed to set bitcoin txId: ", e);
138
- });
139
- return true;
140
- }
141
- return false;
142
- }
143
- async processEventClaim(event, swap) {
144
- if (swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED ||
145
- swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED ||
146
- swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.FRONTED ||
147
- swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
148
- swap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLAIMED;
149
- await swap._setBitcoinTxId(event.btcTxId).catch(e => {
150
- this.logger.warn("processEventClaim(): Failed to set bitcoin txId: ", e);
151
- });
152
- return true;
153
- }
154
- return false;
155
- }
156
- processEventClose(event, swap) {
157
- if (swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED ||
158
- swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED ||
159
- swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
160
- swap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLOSED;
161
- return Promise.resolve(true);
162
- }
163
- return Promise.resolve(false);
164
- }
165
- /**
166
- * @inheritDoc
167
- * @internal
168
- */
169
- async processEvent(event, swap) {
170
- if (swap == null)
171
- return;
172
- let swapChanged = false;
173
- if (event instanceof base_1.SpvVaultFrontEvent) {
174
- swapChanged = await this.processEventFront(event, swap);
175
- if (event.meta?.txId != null && swap._frontTxId !== event.meta.txId) {
176
- swap._frontTxId = event.meta.txId;
177
- swapChanged ||= true;
178
- }
179
- }
180
- if (event instanceof base_1.SpvVaultClaimEvent) {
181
- swapChanged = await this.processEventClaim(event, swap);
182
- if (event.meta?.txId != null && swap._claimTxId !== event.meta.txId) {
183
- swap._claimTxId = event.meta.txId;
184
- swapChanged ||= true;
185
- }
186
- }
187
- if (event instanceof base_1.SpvVaultCloseEvent) {
188
- swapChanged = await this.processEventClose(event, swap);
189
- }
190
- this.logger.info("processEvents(): " + event.constructor.name + " processed for " + swap.getId() + " swap: ", swap);
191
- if (swapChanged) {
192
- await swap._saveAndEmit();
193
- }
194
- }
195
- /**
196
- * Pre-fetches latest finalized block height of the smart chain
197
- *
198
- * @param abortController
199
- * @private
200
- */
201
- async preFetchFinalizedBlockHeight(abortController) {
202
- try {
203
- const block = await this._chain.getFinalizedBlock();
204
- return block.height;
205
- }
206
- catch (e) {
207
- abortController.abort(e);
208
- }
209
- }
210
- /**
211
- * Pre-fetches caller (watchtower) bounty data for the swap. Doesn't throw, instead returns null and aborts the
212
- * provided abortController
213
- *
214
- * @param amountData
215
- * @param options Options as passed to the swap creation function
216
- * @param abortController
217
- * @param contractVersion
218
- * @private
219
- */
220
- async preFetchCallerFeeInNativeToken(amountData, options, abortController, contractVersion) {
221
- if (options.unsafeZeroWatchtowerFee)
222
- return 0n;
223
- if (amountData.amount === 0n)
224
- return 0n;
225
- try {
226
- const [feePerBlock, btcRelayData, currentBtcBlock, claimFeeRate] = await Promise.all([
227
- this.btcRelay(contractVersion).getFeePerBlock(),
228
- this.btcRelay(contractVersion).getTipData(),
229
- this._btcRpc.getTipHeight(),
230
- this._contract(contractVersion).getClaimFee(this._chain.randomAddress())
231
- ]);
232
- if (btcRelayData == null)
233
- throw new Error("Btc relay doesn't seem to be initialized!");
234
- const currentBtcRelayBlock = btcRelayData.blockheight;
235
- const blockDelta = Math.max(currentBtcBlock - currentBtcRelayBlock + this._options.maxConfirmations, 0);
236
- const totalFeeInNativeToken = ((BigInt(blockDelta) * feePerBlock) +
237
- (claimFeeRate * BigInt(this._options.maxTransactionsDelta))) * BigInt(Math.floor(options.feeSafetyFactor * 1000000)) / 1000000n;
238
- return totalFeeInNativeToken;
239
- }
240
- catch (e) {
241
- abortController.abort(e);
242
- }
243
- }
244
- /**
245
- * Pre-fetches caller (watchtower) bounty data for the swap. Doesn't throw, instead returns null and aborts the
246
- * provided abortController
247
- *
248
- * @param amountPrefetch
249
- * @param totalFeeInNativeTokenPrefetch
250
- * @param amountData
251
- * @param options Options as passed to the swap creation function
252
- * @param pricePrefetch
253
- * @param nativeTokenPricePrefetch
254
- * @param abortSignal
255
- * @private
256
- */
257
- async computeCallerFeeShare(amountPrefetch, totalFeeInNativeTokenPrefetch, amountData, options, pricePrefetch, nativeTokenPricePrefetch, abortSignal) {
258
- if (options.unsafeZeroWatchtowerFee)
259
- return 0n;
260
- const amount = await (0, Utils_1.throwIfUndefined)(amountPrefetch, "Cannot get swap amount!");
261
- if (amount === 0n)
262
- return 0n;
263
- const totalFeeInNativeToken = await (0, Utils_1.throwIfUndefined)(totalFeeInNativeTokenPrefetch, "Cannot get total fee in native token!");
264
- const nativeTokenPrice = await nativeTokenPricePrefetch;
265
- let payoutAmount;
266
- if (amountData.exactIn) {
267
- //Convert input amount in BTC to
268
- const amountInNativeToken = await this._prices.getFromBtcSwapAmount(this.chainIdentifier, amount, this._chain.getNativeCurrencyAddress(), abortSignal, nativeTokenPrice);
269
- payoutAmount = amountInNativeToken - totalFeeInNativeToken;
270
- }
271
- else {
272
- if (amountData.token === this._chain.getNativeCurrencyAddress()) {
273
- //Both amounts in same currency
274
- payoutAmount = amount;
275
- }
276
- else {
277
- //Need to convert both to native currency
278
- const btcAmount = await this._prices.getToBtcSwapAmount(this.chainIdentifier, amount, amountData.token, abortSignal, await pricePrefetch);
279
- payoutAmount = await this._prices.getFromBtcSwapAmount(this.chainIdentifier, btcAmount, this._chain.getNativeCurrencyAddress(), abortSignal, nativeTokenPrice);
280
- }
281
- }
282
- this.logger.debug("computeCallerFeeShare(): Caller fee in native token: " + totalFeeInNativeToken.toString(10) + " total payout in native token: " + payoutAmount.toString(10));
283
- const callerFeeShare = ((totalFeeInNativeToken * 100000n) + payoutAmount - 1n) / payoutAmount; //Make sure to round up here
284
- if (callerFeeShare < 0n)
285
- return 0n;
286
- if (callerFeeShare >= 2n ** 20n)
287
- return 2n ** 20n - 1n;
288
- return callerFeeShare;
289
- }
290
- /**
291
- * Verifies response returned from intermediary
292
- *
293
- * @param resp Response as returned by the intermediary
294
- * @param amountData
295
- * @param lp Intermediary
296
- * @param options Options as passed to the swap creation function
297
- * @param callerFeeShare
298
- * @param maxBitcoinFeeRatePromise Maximum accepted fee rate from the LPs
299
- * @param bitcoinFeeRatePromise
300
- * @param abortSignal
301
- * @private
302
- * @throws {IntermediaryError} in case the response is invalid
303
- */
304
- async verifyReturnedData(resp, amountData, lp, options, callerFeeShare, maxBitcoinFeeRatePromise, bitcoinFeeRatePromise, abortSignal) {
305
- const btcFeeRate = await (0, Utils_1.throwIfUndefined)(maxBitcoinFeeRatePromise, "Bitcoin fee rate promise failed!");
306
- abortSignal.throwIfAborted();
307
- if (btcFeeRate != null && resp.btcFeeRate > btcFeeRate)
308
- throw new IntermediaryError_1.IntermediaryError(`Required bitcoin fee rate returned from the LP is too high! Maximum accepted: ${btcFeeRate} sats/vB, required by LP: ${resp.btcFeeRate} sats/vB`);
309
- const lpVersion = lp.getContractVersion(this.chainIdentifier);
310
- //Vault related
311
- let vaultScript;
312
- let vaultAddressType;
313
- let btcAddressScript;
314
- let btcAddressType;
315
- //Ensure valid btc addresses returned
316
- try {
317
- vaultScript = (0, BitcoinUtils_1.toOutputScript)(this._options.bitcoinNetwork, resp.vaultBtcAddress);
318
- vaultAddressType = (0, BitcoinUtils_1.toCoinselectAddressType)(vaultScript);
319
- btcAddressScript = (0, BitcoinUtils_1.toOutputScript)(this._options.bitcoinNetwork, resp.btcAddress);
320
- btcAddressType = (0, BitcoinUtils_1.toCoinselectAddressType)(btcAddressScript);
321
- }
322
- catch (e) {
323
- throw new IntermediaryError_1.IntermediaryError("Invalid btc address data returned", e);
324
- }
325
- const decodedUtxo = resp.btcUtxo.split(":");
326
- if (resp.address !== lp.getAddress(this.chainIdentifier) || //Ensure the LP is indeed the vault owner
327
- resp.vaultId < 0n || //Ensure vaultId is not negative
328
- vaultScript == null || //Make sure vault script is parsable and of known type
329
- btcAddressScript == null || //Make sure btc address script is parsable and of known type
330
- btcAddressType !== exports.REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE || //Constrain the btc address script type
331
- vaultAddressType !== exports.REQUIRED_SPV_SWAP_VAULT_ADDRESS_TYPE || //Constrain the vault script type
332
- decodedUtxo.length !== 2 || decodedUtxo[0].length !== 64 || isNaN(parseInt(decodedUtxo[1])) || //Check valid UTXO
333
- resp.btcFeeRate < 1 || resp.btcFeeRate > 10000 //Sanity check on the returned BTC fee rate
334
- )
335
- throw new IntermediaryError_1.IntermediaryError("Invalid vault data returned!");
336
- //Amounts sanity
337
- if (resp.btcAmountSwap + resp.btcAmountGas !== resp.btcAmount)
338
- throw new Error("Btc amount mismatch");
339
- if (resp.swapFeeBtc + resp.gasSwapFeeBtc !== resp.totalFeeBtc)
340
- throw new Error("Btc fee mismatch");
341
- //TODO: For now ensure fees are at 0
342
- if (resp.callerFeeShare !== callerFeeShare ||
343
- resp.frontingFeeShare !== 0n ||
344
- resp.executionFeeShare !== 0n)
345
- throw new IntermediaryError_1.IntermediaryError("Invalid caller/fronting/execution fee returned");
346
- //Check expiry
347
- const timeNowSeconds = Math.floor(Date.now() / 1000);
348
- if (resp.expiry < timeNowSeconds)
349
- throw new IntermediaryError_1.IntermediaryError(`Quote already expired, expiry: ${resp.expiry}, systemTime: ${timeNowSeconds}, clockAdjusted: ${Date._now != null}`);
350
- let utxo = resp.btcUtxo.toLowerCase();
351
- const [txId, voutStr] = utxo.split(":");
352
- const abortController = (0, Utils_1.extendAbortController)(abortSignal);
353
- let [vault, { vaultUtxoValue, btcTx }] = await Promise.all([
354
- (async () => {
355
- //Fetch vault data
356
- let vault;
357
- try {
358
- vault = await this._contract(lpVersion).getVaultData(resp.address, resp.vaultId);
359
- }
360
- catch (e) {
361
- this.logger.error("Error getting spv vault (owner: " + resp.address + " vaultId: " + resp.vaultId.toString(10) + "): ", e);
362
- throw new IntermediaryError_1.IntermediaryError("Spv swap vault not found", e);
363
- }
364
- abortController.signal.throwIfAborted();
365
- //Make sure vault is opened
366
- if (vault == null || !vault.isOpened())
367
- throw new IntermediaryError_1.IntermediaryError("Returned spv swap vault is not opened!");
368
- //Make sure the vault doesn't require insane amount of confirmations
369
- if (vault.getConfirmations() > this._options.maxConfirmations)
370
- throw new IntermediaryError_1.IntermediaryError("SPV swap vault needs too many confirmations: " + vault.getConfirmations());
371
- const tokenData = vault.getTokenData();
372
- //Amounts - make sure the amounts match
373
- if (amountData.exactIn) {
374
- if (!resp.usedUtxoInputCalculation) {
375
- //Legacy calculation
376
- if (resp.btcAmount !== amountData.amount)
377
- throw new IntermediaryError_1.IntermediaryError("Invalid amount returned");
378
- }
379
- else {
380
- //Implies the raw UTXOs were passed for amount derivation
381
- //Verify the derivation was done correctly
382
- if (options.sourceWalletUtxos == null)
383
- throw new IntermediaryError_1.IntermediaryError("Invalid usedUtxoInputCalcuation return value");
384
- if (bitcoinFeeRatePromise == null)
385
- throw new Error("bitcoinFeeRatePromise must be passed for UTXO-based input amount calculation checks");
386
- const walletUtxos = await options.sourceWalletUtxos;
387
- const bitcoinFeeRate = await (0, Utils_1.throwIfUndefined)(bitcoinFeeRatePromise, "Failed to fetch bitcoin fee rate!");
388
- const { balance } = BitcoinWallet_1.BitcoinWallet.getSpendableBalance(walletUtxos, Math.max(resp.btcFeeRate, bitcoinFeeRate), this.getDummySwapPsbt(options.gasAmount !== 0n), exports.REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE);
389
- if (resp.btcAmount !== balance)
390
- throw new IntermediaryError_1.IntermediaryError(`Invalid amount returned, expected: ${balance.toString(10)}, got: ${resp.btcAmount.toString(10)}`);
391
- }
392
- }
393
- else {
394
- //Check the difference between amount adjusted due to scaling to raw amount
395
- const adjustedAmount = amountData.amount / tokenData[0].multiplier * tokenData[0].multiplier;
396
- const adjustmentPPM = (amountData.amount - adjustedAmount) * 1000000n / amountData.amount;
397
- if (adjustmentPPM > this._options.maxRawAmountAdjustmentDifferencePPM)
398
- throw new IntermediaryError_1.IntermediaryError("Invalid amount0 multiplier used, rawAmount diff too high");
399
- if (resp.total !== adjustedAmount)
400
- throw new IntermediaryError_1.IntermediaryError("Invalid total returned");
401
- }
402
- if (options.gasAmount === 0n) {
403
- if (resp.totalGas !== 0n)
404
- throw new IntermediaryError_1.IntermediaryError("Invalid gas total returned");
405
- }
406
- else {
407
- //Check the difference between amount adjusted due to scaling to raw amount
408
- const adjustedGasAmount = options.gasAmount / tokenData[0].multiplier * tokenData[0].multiplier;
409
- const adjustmentPPM = (options.gasAmount - adjustedGasAmount) * 1000000n / options.gasAmount;
410
- if (adjustmentPPM > this._options.maxRawAmountAdjustmentDifferencePPM)
411
- throw new IntermediaryError_1.IntermediaryError("Invalid amount1 multiplier used, rawAmount diff too high");
412
- if (resp.totalGas !== adjustedGasAmount)
413
- throw new IntermediaryError_1.IntermediaryError("Invalid gas total returned");
414
- }
415
- return vault;
416
- })(),
417
- (async () => {
418
- //Require the vault UTXO to have at least 1 confirmation
419
- let btcTx = await this._btcRpc.getTransaction(txId);
420
- if (btcTx == null)
421
- throw new IntermediaryError_1.IntermediaryError("Invalid UTXO, doesn't exist (txId)");
422
- abortController.signal.throwIfAborted();
423
- if (btcTx.confirmations == null || btcTx.confirmations < 1)
424
- throw new IntermediaryError_1.IntermediaryError("SPV vault UTXO not confirmed");
425
- const vout = parseInt(voutStr);
426
- if (btcTx.outs[vout] == null)
427
- throw new IntermediaryError_1.IntermediaryError("Invalid UTXO, doesn't exist");
428
- const vaultUtxoValue = btcTx.outs[vout].value;
429
- return { btcTx, vaultUtxoValue };
430
- })(),
431
- (async () => {
432
- //Require vault UTXO is unspent
433
- if (await this._btcRpc.isSpent(utxo))
434
- throw new IntermediaryError_1.IntermediaryError("Returned spv vault UTXO is already spent", null, true);
435
- abortController.signal.throwIfAborted();
436
- })()
437
- ]).catch(e => {
438
- abortController.abort(e);
439
- throw e;
440
- });
441
- this.logger.debug("verifyReturnedData(): Vault UTXO: " + vault.getUtxo() + " current utxo: " + utxo);
442
- //Trace returned utxo back to what's saved on-chain
443
- let pendingWithdrawals = [];
444
- while (vault.getUtxo() !== utxo) {
445
- const [txId, voutStr] = utxo.split(":");
446
- //Such that 1st tx isn't fetched twice
447
- if (btcTx.txid !== txId) {
448
- const _btcTx = await this._btcRpc.getTransaction(txId);
449
- if (_btcTx == null)
450
- throw new IntermediaryError_1.IntermediaryError("Invalid ancestor transaction (not found)");
451
- btcTx = _btcTx;
452
- }
453
- const withdrawalData = await this._contract(lpVersion).getWithdrawalData(btcTx);
454
- abortSignal.throwIfAborted();
455
- pendingWithdrawals.unshift(withdrawalData);
456
- utxo = pendingWithdrawals[0].getSpentVaultUtxo();
457
- this.logger.debug("verifyReturnedData(): Vault UTXO: " + vault.getUtxo() + " current utxo: " + utxo);
458
- if (pendingWithdrawals.length >= this._options.maxTransactionsDelta)
459
- throw new IntermediaryError_1.IntermediaryError("BTC <> SC state difference too deep, maximum: " + this._options.maxTransactionsDelta);
460
- }
461
- //Verify that the vault has enough balance after processing all pending withdrawals
462
- let vaultBalances;
463
- try {
464
- vaultBalances = vault.calculateStateAfter(pendingWithdrawals);
465
- }
466
- catch (e) {
467
- this.logger.error("Error calculating spv vault balance (owner: " + resp.address + " vaultId: " + resp.vaultId.toString(10) + "): ", e);
468
- throw new IntermediaryError_1.IntermediaryError("Spv swap vault balance prediction failed", e);
469
- }
470
- if (vaultBalances.balances[0].scaledAmount < resp.total)
471
- throw new IntermediaryError_1.IntermediaryError("SPV swap vault, insufficient balance, required: " + resp.total.toString(10) +
472
- " has: " + vaultBalances.balances[0].scaledAmount.toString(10));
473
- if (vaultBalances.balances[1].scaledAmount < resp.totalGas)
474
- throw new IntermediaryError_1.IntermediaryError("SPV swap vault, insufficient balance, required: " + resp.totalGas.toString(10) +
475
- " has: " + vaultBalances.balances[1].scaledAmount.toString(10));
476
- //Also verify that all the withdrawal txns are valid, this is an extra sanity check
477
- try {
478
- for (let withdrawal of pendingWithdrawals) {
479
- await this._contract(lpVersion).checkWithdrawalTx(withdrawal);
480
- }
481
- }
482
- catch (e) {
483
- this.logger.error("Error calculating spv vault balance (owner: " + resp.address + " vaultId: " + resp.vaultId.toString(10) + "): ", e);
484
- throw new IntermediaryError_1.IntermediaryError("Spv swap vault balance prediction failed", e);
485
- }
486
- abortSignal.throwIfAborted();
487
- return {
488
- vault,
489
- vaultUtxoValue
490
- };
491
- }
492
- async amountPrefetch(amountData, bitcoinFeeRatePromise, walletUtxosPromise, includeGas, abortController) {
493
- if (amountData.amount != null)
494
- return amountData.amount;
495
- try {
496
- const bitcoinFeeRate = await (0, Utils_1.throwIfUndefined)(bitcoinFeeRatePromise, "Cannot fetch Bitcoin fee rate!");
497
- if (walletUtxosPromise == null)
498
- throw new UserError_1.UserError("Cannot use empty amount without passing UTXOs!");
499
- const walletUtxos = await walletUtxosPromise;
500
- if (walletUtxos.length === 0)
501
- throw new UserError_1.UserError("Wallet doesn't have any BTC balance");
502
- const spendableBalance = await BitcoinWallet_1.BitcoinWallet.getSpendableBalance(walletUtxos, bitcoinFeeRate, this.getDummySwapPsbt(includeGas), exports.REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE);
503
- return spendableBalance.balance;
504
- }
505
- catch (e) {
506
- abortController.abort(e);
507
- }
508
- }
509
- bitcoinFeeRatePrefetch(options, abortController) {
510
- let bitcoinFeeRatePromise;
511
- if (options?.sourceWalletUtxos != null) {
512
- if (options.bitcoinFeeRate != null) {
513
- bitcoinFeeRatePromise = options.bitcoinFeeRate.then(value => {
514
- if (options.maxAllowedBitcoinFeeRate != Infinity && options.maxAllowedBitcoinFeeRate < value)
515
- throw new Error("Passed `maxAllowedBitcoinFeeRate` cannot be lower than `bitcoinFeeRate`");
516
- return value;
517
- });
518
- }
519
- else {
520
- bitcoinFeeRatePromise = this._btcRpc.getFeeRate().then(value => {
521
- if (options.maxAllowedBitcoinFeeRate != Infinity && value > options.maxAllowedBitcoinFeeRate)
522
- return options.maxAllowedBitcoinFeeRate;
523
- return value;
524
- });
525
- }
526
- bitcoinFeeRatePromise = bitcoinFeeRatePromise.catch(e => {
527
- abortController.abort(e);
528
- return undefined;
529
- });
530
- }
531
- const maxBitcoinFeeRatePromise = options.maxAllowedBitcoinFeeRate != Infinity
532
- ? Promise.resolve(options.maxAllowedBitcoinFeeRate)
533
- : (0, Utils_1.throwIfUndefined)(bitcoinFeeRatePromise ?? options.bitcoinFeeRate ?? this._btcRpc.getFeeRate())
534
- .then(x => this._options.maxBtcFeeOffset + (x * this._options.maxBtcFeeMultiplier))
535
- .catch(e => {
536
- abortController.abort(e);
537
- return undefined;
538
- });
539
- return {
540
- bitcoinFeeRatePromise,
541
- maxBitcoinFeeRatePromise
542
- };
543
- }
544
- /**
545
- * Returns a newly created Bitcoin -> Smart chain swap using the SPV vault (UTXO-controlled vault) swap protocol,
546
- * with the passed amount. Also allows specifying additional "gas drop" native token that the receipient receives
547
- * on the destination chain in the `options` argument.
548
- *
549
- * @param recipient Recipient address on the destination smart chain
550
- * @param amountData Amount, token and exact input/output data for to swap
551
- * @param lps An array of intermediaries (LPs) to get the quotes from
552
- * @param options Optional additional quote options
553
- * @param additionalParams Optional additional parameters sent to the LP when creating the swap
554
- * @param abortSignal Abort signal
555
- */
556
- create(recipient, amountData, lps, options, additionalParams, abortSignal) {
557
- const _options = {
558
- gasAmount: this.parseGasAmount(options?.gasAmount),
559
- unsafeZeroWatchtowerFee: options?.unsafeZeroWatchtowerFee ?? false,
560
- feeSafetyFactor: options?.feeSafetyFactor ?? 1.25,
561
- maxAllowedBitcoinFeeRate: options?.maxAllowedBitcoinFeeRate ?? options?.maxAllowedNetworkFeeRate ?? Infinity,
562
- sourceWalletUtxos: options?.sourceWalletUtxos == undefined
563
- ? undefined
564
- : options?.sourceWalletUtxos instanceof Promise ? options.sourceWalletUtxos : Promise.resolve(options.sourceWalletUtxos),
565
- bitcoinFeeRate: options?.bitcoinFeeRate == undefined
566
- ? undefined
567
- : options?.bitcoinFeeRate instanceof Promise ? options.bitcoinFeeRate : Promise.resolve(options.bitcoinFeeRate),
568
- };
569
- if (_options.gasAmount !== 0n &&
570
- (this._chain.shouldGetNativeTokenDrop != null
571
- ? !this._chain.shouldGetNativeTokenDrop(amountData.token)
572
- : amountData.token === this._chain.getNativeCurrencyAddress()))
573
- throw new UserError_1.UserError("Cannot specify `gasAmount` for swaps to a native token!");
574
- if (amountData.amount == null && options?.sourceWalletUtxos == null)
575
- throw new UserError_1.UserError("Source wallet UTXOs need to be passed when amount is null!");
576
- if (amountData.amount == null && !amountData.exactIn)
577
- throw new UserError_1.UserError("Amount can be null only for exactIn swaps!");
578
- if (amountData.amount != null && options?.sourceWalletUtxos != null)
579
- throw new UserError_1.UserError("Source wallet UTXOs cannot be passed while specifying an input amount!");
580
- const lpVersions = Intermediary_1.Intermediary.getContractVersionsForLps(this.chainIdentifier, lps);
581
- const _abortController = (0, Utils_1.extendAbortController)(abortSignal);
582
- const pricePrefetchPromise = this.preFetchPrice(amountData, _abortController.signal);
583
- const usdPricePrefetchPromise = this.preFetchUsdPrice(_abortController.signal);
584
- const finalizedBlockHeightPrefetchPromise = this.preFetchFinalizedBlockHeight(_abortController);
585
- const nativeTokenAddress = this._chain.getNativeCurrencyAddress();
586
- const gasTokenPricePrefetchPromise = _options.gasAmount === 0n ?
587
- undefined :
588
- this.preFetchPrice({ token: nativeTokenAddress }, _abortController.signal);
589
- const callerFeePrefetchPromise = (0, Utils_1.mapArrayToObject)(lpVersions, (contractVersion) => {
590
- return this.preFetchCallerFeeInNativeToken(amountData, _options, _abortController, contractVersion);
591
- });
592
- const { maxBitcoinFeeRatePromise, bitcoinFeeRatePromise } = this.bitcoinFeeRatePrefetch(_options, _abortController);
593
- const amountPromise = this.amountPrefetch(amountData, maxBitcoinFeeRatePromise, _options.sourceWalletUtxos, _options.gasAmount !== 0n, _abortController);
594
- return lps.map(lp => {
595
- return {
596
- intermediary: lp,
597
- quote: (0, RetryUtils_1.tryWithRetries)(async () => {
598
- if (lp.services[SwapType_1.SwapType.SPV_VAULT_FROM_BTC] == null)
599
- throw new Error("LP service for processing spv vault swaps not found!");
600
- const version = lp.getContractVersion(this.chainIdentifier);
601
- const abortController = (0, Utils_1.extendAbortController)(_abortController.signal);
602
- const callerFeeRatePromise = this.computeCallerFeeShare(amountPromise, callerFeePrefetchPromise[version], amountData, _options, pricePrefetchPromise, gasTokenPricePrefetchPromise, abortController.signal);
603
- try {
604
- const resp = await (0, RetryUtils_1.tryWithRetries)(async (retryCount) => {
605
- return await this._lpApi.prepareSpvFromBTC(this.chainIdentifier, lp.url, {
606
- address: recipient,
607
- amount: (0, Utils_1.throwIfUndefined)(amountPromise, "Failed to compute swap amount"),
608
- token: amountData.token.toString(),
609
- exactOut: !amountData.exactIn,
610
- gasToken: nativeTokenAddress,
611
- gasAmount: _options.gasAmount,
612
- callerFeeRate: (0, Utils_1.throwIfUndefined)(callerFeeRatePromise, "Caller fee prefetch failed!"),
613
- frontingFeeRate: 0n,
614
- stickyAddress: options?.stickyAddress,
615
- amountUtxos: _options.sourceWalletUtxos != null
616
- ? _options.sourceWalletUtxos.then(utxos => {
617
- if (utxos.length === 0)
618
- return undefined;
619
- return utxos.map(utxo => ({
620
- value: utxo.value,
621
- vSize: utils_2.utils.inputBytes({ type: utxo.type }),
622
- cpfp: utxo.cpfp == null ? undefined : { effectiveVSize: utxo.cpfp?.txVsize, effectiveFeeRate: utxo.cpfp?.txEffectiveFeeRate }
623
- }));
624
- })
625
- : undefined,
626
- amountFeeRate: bitcoinFeeRatePromise,
627
- additionalParams
628
- }, this._options.postRequestTimeout, abortController.signal, retryCount > 0 ? false : undefined);
629
- }, undefined, e => e instanceof RequestError_1.RequestError, abortController.signal);
630
- this.logger.debug("create(" + lp.url + "): LP response: ", resp);
631
- const callerFeeShare = await callerFeeRatePromise;
632
- const amount = await (0, Utils_1.throwIfUndefined)(amountPromise);
633
- const [pricingInfo, gasPricingInfo, { vault, vaultUtxoValue }] = await Promise.all([
634
- this.verifyReturnedPrice(lp.services[SwapType_1.SwapType.SPV_VAULT_FROM_BTC], false, resp.btcAmountSwap, resp.total * (100000n + callerFeeShare) / 100000n, amountData.token, { swapFeeBtc: resp.swapFeeBtc }, pricePrefetchPromise, usdPricePrefetchPromise, abortController.signal),
635
- _options.gasAmount === 0n ? Promise.resolve(undefined) : this.verifyReturnedPrice({ ...lp.services[SwapType_1.SwapType.SPV_VAULT_FROM_BTC], swapBaseFee: 0 }, //Base fee should be charged only on the amount, not on gas
636
- false, resp.btcAmountGas, resp.totalGas * (100000n + callerFeeShare) / 100000n, nativeTokenAddress, { swapFeeBtc: resp.gasSwapFeeBtc }, gasTokenPricePrefetchPromise, usdPricePrefetchPromise, abortController.signal),
637
- this.verifyReturnedData(resp, { ...amountData, amount }, lp, _options, callerFeeShare, maxBitcoinFeeRatePromise, bitcoinFeeRatePromise, abortController.signal)
638
- ]);
639
- let minimumBtcFeeRate = resp.btcFeeRate;
640
- if (bitcoinFeeRatePromise != null)
641
- minimumBtcFeeRate = Math.max(minimumBtcFeeRate, await (0, Utils_1.throwIfUndefined)(bitcoinFeeRatePromise));
642
- const swapInit = {
643
- pricingInfo,
644
- url: lp.url,
645
- expiry: resp.expiry * 1000,
646
- swapFee: resp.swapFee,
647
- swapFeeBtc: resp.swapFeeBtc,
648
- exactIn: amountData.exactIn ?? true,
649
- quoteId: resp.quoteId,
650
- recipient,
651
- vaultOwner: resp.address,
652
- vaultId: resp.vaultId,
653
- vaultRequiredConfirmations: vault.getConfirmations(),
654
- vaultTokenMultipliers: vault.getTokenData().map(val => val.multiplier),
655
- vaultBtcAddress: resp.vaultBtcAddress,
656
- vaultUtxo: resp.btcUtxo,
657
- vaultUtxoValue: BigInt(vaultUtxoValue),
658
- btcDestinationAddress: resp.btcAddress,
659
- btcAmount: resp.btcAmount,
660
- btcAmountSwap: resp.btcAmountSwap,
661
- btcAmountGas: resp.btcAmountGas,
662
- minimumBtcFeeRate,
663
- outputTotalSwap: resp.total,
664
- outputSwapToken: amountData.token,
665
- outputTotalGas: resp.totalGas,
666
- outputGasToken: nativeTokenAddress,
667
- gasSwapFeeBtc: resp.gasSwapFeeBtc,
668
- gasSwapFee: resp.gasSwapFee,
669
- gasPricingInfo,
670
- callerFeeShare: resp.callerFeeShare,
671
- frontingFeeShare: resp.frontingFeeShare,
672
- executionFeeShare: resp.executionFeeShare,
673
- genesisSmartChainBlockHeight: await (0, Utils_1.throwIfUndefined)(finalizedBlockHeightPrefetchPromise, "Network finalized blockheight pre-fetch failed!"),
674
- contractVersion: version
675
- };
676
- const quote = new SpvFromBTCSwap_1.SpvFromBTCSwap(this, swapInit);
677
- return quote;
678
- }
679
- catch (e) {
680
- if (e instanceof RequestError_1.OutOfBoundsError) {
681
- const amountResult = await amountPromise.catch(() => undefined);
682
- if (_options.sourceWalletUtxos != null && amountResult != null && amountResult <= 0n) {
683
- e = new UserError_1.UserError("Wallet doesn't have enough BTC balance to cover transaction fees");
684
- }
685
- }
686
- abortController.abort(e);
687
- throw e;
688
- }
689
- }, undefined, err => !(err instanceof IntermediaryError_1.IntermediaryError && err.recoverable), _abortController.signal)
690
- };
691
- });
692
- }
693
- /**
694
- * Recovers an SPV vault (UTXO-controlled vault) based swap from smart chain on-chain data
695
- *
696
- * @param state State of the spv vault withdrawal recovered from on-chain data
697
- * @param vault SPV vault processing the swap
698
- * @param lp Intermediary (LP) used as a counterparty for the swap
699
- */
700
- async recoverFromState(state, contractVersion, vault, lp) {
701
- //Get the vault
702
- vault ??= await this._contract(contractVersion).getVaultData(state.owner, state.vaultId);
703
- if (vault == null)
704
- return null;
705
- if (state.btcTxId == null)
706
- return null;
707
- const btcTx = await this._btcRpc.getTransaction(state.btcTxId);
708
- if (btcTx == null)
709
- return null;
710
- const withdrawalData = await this._contract(contractVersion).getWithdrawalData(btcTx)
711
- .catch(e => {
712
- this.logger.warn(`Error parsing withdrawal data for tx ${btcTx.txid}: `, e);
713
- return null;
714
- });
715
- if (withdrawalData == null)
716
- return null;
717
- const vaultTokens = vault.getTokenData();
718
- const withdrawalDataOutputs = withdrawalData.getTotalOutput();
719
- const txBlock = await state.getTxBlock?.();
720
- const swapInit = {
721
- pricingInfo: {
722
- isValid: true,
723
- satsBaseFee: 0n,
724
- swapPriceUSatPerToken: 100000000000000n,
725
- realPriceUSatPerToken: 100000000000000n,
726
- differencePPM: 0n,
727
- feePPM: 0n,
728
- },
729
- url: lp?.url,
730
- expiry: 0,
731
- swapFee: 0n,
732
- swapFeeBtc: 0n,
733
- exactIn: true,
734
- //Use bitcoin tx id as quote id, even though this is not strictly correct as this
735
- // is an off-chain identifier presented by the LP that cannot be recovered from on-chain
736
- // data
737
- quoteId: btcTx.txid,
738
- recipient: state.recipient,
739
- vaultOwner: state.owner,
740
- vaultId: state.vaultId,
741
- vaultRequiredConfirmations: vault.getConfirmations(),
742
- vaultTokenMultipliers: vault.getTokenData().map(val => val.multiplier),
743
- vaultBtcAddress: (0, BitcoinUtils_1.fromOutputScript)(this._options.bitcoinNetwork, withdrawalData.getNewVaultScript().toString("hex")),
744
- vaultUtxo: withdrawalData.getSpentVaultUtxo(),
745
- vaultUtxoValue: BigInt(withdrawalData.getNewVaultBtcAmount()),
746
- btcDestinationAddress: (0, BitcoinUtils_1.fromOutputScript)(this._options.bitcoinNetwork, btcTx.outs[2].scriptPubKey.hex),
747
- btcAmount: BigInt(btcTx.outs[2].value),
748
- btcAmountSwap: BigInt(btcTx.outs[2].value),
749
- btcAmountGas: 0n,
750
- minimumBtcFeeRate: 0,
751
- outputTotalSwap: withdrawalDataOutputs[0] * vaultTokens[0].multiplier,
752
- outputSwapToken: vaultTokens[0].token,
753
- outputTotalGas: withdrawalDataOutputs[1] * vaultTokens[1].multiplier,
754
- outputGasToken: vaultTokens[1].token,
755
- gasSwapFeeBtc: 0n,
756
- gasSwapFee: 0n,
757
- gasPricingInfo: {
758
- isValid: true,
759
- satsBaseFee: 0n,
760
- swapPriceUSatPerToken: 100000000000000n,
761
- realPriceUSatPerToken: 100000000000000n,
762
- differencePPM: 0n,
763
- feePPM: 0n,
764
- },
765
- callerFeeShare: withdrawalData.callerFeeRate,
766
- frontingFeeShare: withdrawalData.frontingFeeRate,
767
- executionFeeShare: withdrawalData.executionFeeRate,
768
- genesisSmartChainBlockHeight: txBlock?.blockHeight ?? 0,
769
- contractVersion
770
- };
771
- const quote = new SpvFromBTCSwap_1.SpvFromBTCSwap(this, swapInit);
772
- quote._data = withdrawalData;
773
- if (txBlock != null) {
774
- quote.createdAt = txBlock.blockTime * 1000;
775
- }
776
- else if (btcTx.blockhash == null) {
777
- quote.createdAt = Date.now();
778
- }
779
- else {
780
- const blockHeader = await this._btcRpc.getBlockHeader(btcTx.blockhash);
781
- quote.createdAt = blockHeader == null ? Date.now() : blockHeader.getTimestamp() * 1000;
782
- }
783
- quote._setInitiated();
784
- if (btcTx.inputAddresses != null)
785
- quote._senderAddress = btcTx.inputAddresses[1];
786
- if (state.type === base_1.SpvWithdrawalStateType.FRONTED) {
787
- quote._frontTxId = state.txId;
788
- quote._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.FRONTED;
789
- }
790
- else {
791
- quote._claimTxId = state.txId;
792
- quote._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLAIMED;
793
- }
794
- await quote._save();
795
- return quote;
796
- }
797
- /**
798
- * Returns a random dummy PSBT that can be used for fee estimation, the last output (the LP output) is omitted
799
- * to allow for coinselection algorithm to determine maximum sendable amount there
800
- *
801
- * @param includeGasToken Whether to return the PSBT also with the gas token amount (increases the vSize by 8)
802
- */
803
- getDummySwapPsbt(includeGasToken = false) {
804
- //Construct dummy swap psbt
805
- const psbt = new btc_signer_1.Transaction({
806
- allowUnknownInputs: true,
807
- allowLegacyWitnessUtxo: true,
808
- allowUnknownOutputs: true
809
- });
810
- const randomVaultOutScript = (0, BitcoinUtils_1.getDummyOutputScript)(exports.REQUIRED_SPV_SWAP_VAULT_ADDRESS_TYPE);
811
- psbt.addInput({
812
- txid: (0, Utils_1.randomBytes)(32),
813
- index: 0,
814
- witnessUtxo: {
815
- script: randomVaultOutScript,
816
- amount: 600n
817
- }
818
- });
819
- psbt.addOutput({
820
- script: randomVaultOutScript,
821
- amount: 600n
822
- });
823
- let longestOpReturnData = undefined;
824
- for (let contractVersion in this.versionedContracts) {
825
- if (this.versionedContracts[contractVersion].spvVaultContract == null)
826
- continue;
827
- const opReturnData = this._contract(contractVersion).toOpReturnData(this._chain.randomAddress(), includeGasToken ? [0xffffffffffffffffn, 0xffffffffffffffffn] : [0xffffffffffffffffn]);
828
- if (longestOpReturnData == null || longestOpReturnData.length < opReturnData.length)
829
- longestOpReturnData = opReturnData;
830
- }
831
- if (longestOpReturnData == null)
832
- throw new Error(`No contract version supporting the Spv Vault BTC -> ${this.chainIdentifier} swaps found!`);
833
- psbt.addOutput({
834
- script: Buffer.concat([
835
- longestOpReturnData.length <= 75 ? Buffer.from([0x6a, longestOpReturnData.length]) : Buffer.from([0x6a, 0x4c, longestOpReturnData.length]),
836
- longestOpReturnData
837
- ]),
838
- amount: 0n
839
- });
840
- return psbt;
841
- }
842
- /**
843
- * @inheritDoc
844
- * @internal
845
- */
846
- async _checkPastSwaps(pastSwaps) {
847
- const changedSwaps = new Set();
848
- const removeSwaps = [];
849
- const broadcastedOrConfirmedSwaps = {};
850
- for (let pastSwap of pastSwaps) {
851
- let changed = false;
852
- if (pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED ||
853
- pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED ||
854
- pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED ||
855
- pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED ||
856
- pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED ||
857
- pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
858
- //Check BTC transaction
859
- if (await pastSwap._syncStateFromBitcoin(false))
860
- changed ||= true;
861
- }
862
- if (pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.CREATED ||
863
- pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED ||
864
- pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED) {
865
- if (await pastSwap._verifyQuoteDefinitelyExpired()) {
866
- if (pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.CREATED) {
867
- pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_EXPIRED;
868
- }
869
- else {
870
- pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED;
871
- }
872
- changed ||= true;
873
- }
874
- }
875
- if (pastSwap.isQuoteExpired()) {
876
- removeSwaps.push(pastSwap);
877
- continue;
878
- }
879
- if (changed)
880
- changedSwaps.add(pastSwap);
881
- if (pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED || pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
882
- if (pastSwap._data != null)
883
- (broadcastedOrConfirmedSwaps[pastSwap._contractVersion ?? "v1"] ??= []).push(pastSwap);
884
- }
885
- }
886
- for (let contractVersion in broadcastedOrConfirmedSwaps) {
887
- if (this.versionedContracts[contractVersion] == null) {
888
- this.logger.warn(`_checkPastSwaps(): No contract was found for ${this.chainIdentifier} version ${contractVersion}! Skipping these swaps!`);
889
- continue;
890
- }
891
- const _broadcastedOrConfirmedSwaps = broadcastedOrConfirmedSwaps[contractVersion];
892
- const checkWithdrawalStateSwaps = [];
893
- const _fronts = await this._contract(contractVersion).getFronterAddresses(_broadcastedOrConfirmedSwaps.map(val => ({
894
- ...val.getSpvVaultData(),
895
- withdrawal: val._data
896
- })));
897
- const _vaultUtxos = await this._contract(contractVersion).getVaultLatestUtxos(_broadcastedOrConfirmedSwaps.map(val => val.getSpvVaultData()));
898
- for (const pastSwap of _broadcastedOrConfirmedSwaps) {
899
- const fronterAddress = _fronts[pastSwap._data.getTxId()];
900
- const vault = pastSwap.getSpvVaultData();
901
- const latestVaultUtxo = _vaultUtxos[vault.owner]?.[vault.vaultId.toString(10)];
902
- if (fronterAddress === undefined)
903
- this.logger.warn(`_checkPastSwaps(): No fronter address returned for ${pastSwap._data.getTxId()}`);
904
- if (latestVaultUtxo === undefined)
905
- this.logger.warn(`_checkPastSwaps(): No last vault utxo returned for ${pastSwap._data.getTxId()}`);
906
- if (await pastSwap._shouldCheckWithdrawalState(fronterAddress, latestVaultUtxo))
907
- checkWithdrawalStateSwaps.push(pastSwap);
908
- }
909
- const withdrawalStates = await this._contract(contractVersion).getWithdrawalStates(checkWithdrawalStateSwaps.map(val => ({
910
- withdrawal: val._data,
911
- scStartBlockheight: val._genesisSmartChainBlockHeight
912
- })));
913
- for (const pastSwap of checkWithdrawalStateSwaps) {
914
- const status = withdrawalStates[pastSwap._data.getTxId()];
915
- if (status == null) {
916
- this.logger.warn(`_checkPastSwaps(): No withdrawal state returned for ${pastSwap._data.getTxId()}`);
917
- continue;
918
- }
919
- this.logger.debug("syncStateFromChain(): status of " + pastSwap._data.btcTx.txid, status?.type);
920
- let changed = false;
921
- switch (status.type) {
922
- case base_1.SpvWithdrawalStateType.FRONTED:
923
- pastSwap._frontTxId = status.txId;
924
- pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.FRONTED;
925
- changed ||= true;
926
- break;
927
- case base_1.SpvWithdrawalStateType.CLAIMED:
928
- pastSwap._claimTxId = status.txId;
929
- pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLAIMED;
930
- changed ||= true;
931
- break;
932
- case base_1.SpvWithdrawalStateType.CLOSED:
933
- pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLOSED;
934
- changed ||= true;
935
- break;
936
- }
937
- if (changed)
938
- changedSwaps.add(pastSwap);
939
- }
940
- }
941
- return {
942
- changedSwaps: Array.from(changedSwaps),
943
- removeSwaps
944
- };
945
- }
946
- }
947
- exports.SpvFromBTCWrapper = SpvFromBTCWrapper;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SpvFromBTCWrapper = exports.REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE = exports.REQUIRED_SPV_SWAP_VAULT_ADDRESS_TYPE = void 0;
4
+ const ISwapWrapper_1 = require("../ISwapWrapper");
5
+ const base_1 = require("@atomiqlabs/base");
6
+ const SpvFromBTCSwap_1 = require("./SpvFromBTCSwap");
7
+ const utils_1 = require("@scure/btc-signer/utils");
8
+ const SwapType_1 = require("../../enums/SwapType");
9
+ const Intermediary_1 = require("../../intermediaries/Intermediary");
10
+ const Utils_1 = require("../../utils/Utils");
11
+ const BitcoinUtils_1 = require("../../utils/BitcoinUtils");
12
+ const RequestError_1 = require("../../errors/RequestError");
13
+ const IntermediaryError_1 = require("../../errors/IntermediaryError");
14
+ const btc_signer_1 = require("@scure/btc-signer");
15
+ const RetryUtils_1 = require("../../utils/RetryUtils");
16
+ const UserError_1 = require("../../errors/UserError");
17
+ const utils_2 = require("../../bitcoin/coinselect2/utils");
18
+ const BitcoinWallet_1 = require("../../bitcoin/wallet/BitcoinWallet");
19
+ exports.REQUIRED_SPV_SWAP_VAULT_ADDRESS_TYPE = "p2tr";
20
+ exports.REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE = "p2wpkh";
21
+ /**
22
+ * New spv vault (UTXO-controlled vault) based swaps for Bitcoin -> Smart chain swaps not requiring
23
+ * any initiation on the destination chain, and with the added possibility for the user to receive
24
+ * a native token on the destination chain as part of the swap (a "gas drop" feature).
25
+ *
26
+ * @category Swaps/Bitcoin → Smart chain
27
+ */
28
+ class SpvFromBTCWrapper extends ISwapWrapper_1.ISwapWrapper {
29
+ /**
30
+ * @param chainIdentifier
31
+ * @param unifiedStorage Storage interface for the current environment
32
+ * @param unifiedChainEvents On-chain event listener
33
+ * @param chain
34
+ * @param prices Pricing to use
35
+ * @param tokens
36
+ * @param versionedContracts
37
+ * @param versionedSynchronizer
38
+ * @param btcRpc Bitcoin RPC which also supports getting transactions by txoHash
39
+ * @param lpApi
40
+ * @param options
41
+ * @param events Instance to use for emitting events
42
+ */
43
+ constructor(chainIdentifier, unifiedStorage, unifiedChainEvents, chain, prices, tokens, versionedContracts, versionedSynchronizer, btcRpc, lpApi, options, events) {
44
+ super(chainIdentifier, unifiedStorage, unifiedChainEvents, chain, prices, tokens, lpApi, {
45
+ ...options,
46
+ bitcoinNetwork: options?.bitcoinNetwork ?? utils_1.TEST_NETWORK,
47
+ maxConfirmations: options?.maxConfirmations ?? 6,
48
+ bitcoinBlocktime: options?.bitcoinBlocktime ?? 10 * 60,
49
+ maxTransactionsDelta: options?.maxTransactionsDelta ?? 3,
50
+ maxRawAmountAdjustmentDifferencePPM: options?.maxRawAmountAdjustmentDifferencePPM ?? 100,
51
+ maxBtcFeeOffset: options?.maxBtcFeeOffset ?? 10,
52
+ maxBtcFeeMultiplier: options?.maxBtcFeeMultiplier ?? 1.5
53
+ }, events);
54
+ this.TYPE = SwapType_1.SwapType.SPV_VAULT_FROM_BTC;
55
+ /**
56
+ * @internal
57
+ */
58
+ this._claimableSwapStates = [SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED];
59
+ /**
60
+ * @internal
61
+ */
62
+ this._swapDeserializer = SpvFromBTCSwap_1.SpvFromBTCSwap;
63
+ /**
64
+ * @internal
65
+ */
66
+ this.btcRelay = (version) => {
67
+ const _version = version ?? "v1";
68
+ const data = this.versionedContracts[_version];
69
+ if (data == null)
70
+ throw new Error(`Invalid contract version ${_version} requested`);
71
+ return data.btcRelay;
72
+ };
73
+ /**
74
+ * @internal
75
+ */
76
+ this.tickSwapState = [
77
+ SpvFromBTCSwap_1.SpvFromBTCSwapState.CREATED,
78
+ SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED,
79
+ SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED,
80
+ SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED,
81
+ SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED
82
+ ];
83
+ /**
84
+ * @internal
85
+ */
86
+ this._synchronizer = (version) => {
87
+ const _version = version ?? "v1";
88
+ const data = this.versionedSynchronizer[_version];
89
+ if (data == null)
90
+ throw new Error(`Invalid contract version ${_version} requested`);
91
+ return data.synchronizer;
92
+ };
93
+ /**
94
+ * @internal
95
+ */
96
+ this._contract = (version) => {
97
+ const _version = version ?? "v1";
98
+ const data = this.versionedContracts[_version];
99
+ if (data == null)
100
+ throw new Error(`Invalid contract version ${_version} requested`);
101
+ return data.spvVaultContract;
102
+ };
103
+ /**
104
+ * @internal
105
+ */
106
+ this._spvWithdrawalDataDeserializer = (version) => {
107
+ const _version = version ?? "v1";
108
+ const data = this.versionedContracts[_version];
109
+ if (data == null)
110
+ throw new Error(`Invalid contract version ${_version} requested`);
111
+ return data.spvVaultWithdrawalDataConstructor;
112
+ };
113
+ /**
114
+ * @internal
115
+ */
116
+ this._pendingSwapStates = [
117
+ SpvFromBTCSwap_1.SpvFromBTCSwapState.CREATED,
118
+ SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED,
119
+ SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED,
120
+ SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED,
121
+ SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED,
122
+ SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED,
123
+ SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED
124
+ ];
125
+ this.versionedContracts = {};
126
+ this.versionedSynchronizer = {};
127
+ this.versionedContracts = versionedContracts;
128
+ this.versionedSynchronizer = versionedSynchronizer;
129
+ this._btcRpc = btcRpc;
130
+ }
131
+ async processEventFront(event, swap) {
132
+ if (swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED ||
133
+ swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED ||
134
+ swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
135
+ swap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.FRONTED;
136
+ await swap._setBitcoinTxId(event.btcTxId).catch(e => {
137
+ this.logger.warn("processEventFront(): Failed to set bitcoin txId: ", e);
138
+ });
139
+ return true;
140
+ }
141
+ return false;
142
+ }
143
+ async processEventClaim(event, swap) {
144
+ if (swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED ||
145
+ swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED ||
146
+ swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.FRONTED ||
147
+ swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
148
+ swap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLAIMED;
149
+ await swap._setBitcoinTxId(event.btcTxId).catch(e => {
150
+ this.logger.warn("processEventClaim(): Failed to set bitcoin txId: ", e);
151
+ });
152
+ return true;
153
+ }
154
+ return false;
155
+ }
156
+ processEventClose(event, swap) {
157
+ if (swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED ||
158
+ swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED ||
159
+ swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
160
+ swap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLOSED;
161
+ return Promise.resolve(true);
162
+ }
163
+ return Promise.resolve(false);
164
+ }
165
+ /**
166
+ * @inheritDoc
167
+ * @internal
168
+ */
169
+ async processEvent(event, swap) {
170
+ if (swap == null)
171
+ return;
172
+ let swapChanged = false;
173
+ if (event instanceof base_1.SpvVaultFrontEvent) {
174
+ swapChanged = await this.processEventFront(event, swap);
175
+ if (event.meta?.txId != null && swap._frontTxId !== event.meta.txId) {
176
+ swap._frontTxId = event.meta.txId;
177
+ swapChanged ||= true;
178
+ }
179
+ }
180
+ if (event instanceof base_1.SpvVaultClaimEvent) {
181
+ swapChanged = await this.processEventClaim(event, swap);
182
+ if (event.meta?.txId != null && swap._claimTxId !== event.meta.txId) {
183
+ swap._claimTxId = event.meta.txId;
184
+ swapChanged ||= true;
185
+ }
186
+ }
187
+ if (event instanceof base_1.SpvVaultCloseEvent) {
188
+ swapChanged = await this.processEventClose(event, swap);
189
+ }
190
+ this.logger.info("processEvents(): " + event.constructor.name + " processed for " + swap.getId() + " swap: ", swap);
191
+ if (swapChanged) {
192
+ await swap._saveAndEmit();
193
+ }
194
+ }
195
+ /**
196
+ * Pre-fetches latest finalized block height of the smart chain
197
+ *
198
+ * @param abortController
199
+ * @private
200
+ */
201
+ async preFetchFinalizedBlockHeight(abortController) {
202
+ try {
203
+ const block = await this._chain.getFinalizedBlock();
204
+ return block.height;
205
+ }
206
+ catch (e) {
207
+ abortController.abort(e);
208
+ }
209
+ }
210
+ /**
211
+ * Pre-fetches caller (watchtower) bounty data for the swap. Doesn't throw, instead returns null and aborts the
212
+ * provided abortController
213
+ *
214
+ * @param amountData
215
+ * @param options Options as passed to the swap creation function
216
+ * @param abortController
217
+ * @param contractVersion
218
+ * @private
219
+ */
220
+ async preFetchCallerFeeInNativeToken(amountData, options, abortController, contractVersion) {
221
+ if (options.unsafeZeroWatchtowerFee)
222
+ return 0n;
223
+ if (amountData.amount === 0n)
224
+ return 0n;
225
+ try {
226
+ const [feePerBlock, btcRelayData, currentBtcBlock, claimFeeRate] = await Promise.all([
227
+ this.btcRelay(contractVersion).getFeePerBlock(),
228
+ this.btcRelay(contractVersion).getTipData(),
229
+ this._btcRpc.getTipHeight(),
230
+ this._contract(contractVersion).getClaimFee(this._chain.randomAddress())
231
+ ]);
232
+ if (btcRelayData == null)
233
+ throw new Error("Btc relay doesn't seem to be initialized!");
234
+ const currentBtcRelayBlock = btcRelayData.blockheight;
235
+ const blockDelta = Math.max(currentBtcBlock - currentBtcRelayBlock + this._options.maxConfirmations, 0);
236
+ const totalFeeInNativeToken = ((BigInt(blockDelta) * feePerBlock) +
237
+ (claimFeeRate * BigInt(this._options.maxTransactionsDelta))) * BigInt(Math.floor(options.feeSafetyFactor * 1000000)) / 1000000n;
238
+ return totalFeeInNativeToken;
239
+ }
240
+ catch (e) {
241
+ abortController.abort(e);
242
+ }
243
+ }
244
+ /**
245
+ * Pre-fetches caller (watchtower) bounty data for the swap. Doesn't throw, instead returns null and aborts the
246
+ * provided abortController
247
+ *
248
+ * @param amountPrefetch
249
+ * @param totalFeeInNativeTokenPrefetch
250
+ * @param amountData
251
+ * @param options Options as passed to the swap creation function
252
+ * @param pricePrefetch
253
+ * @param nativeTokenPricePrefetch
254
+ * @param abortSignal
255
+ * @private
256
+ */
257
+ async computeCallerFeeShare(amountPrefetch, totalFeeInNativeTokenPrefetch, amountData, options, pricePrefetch, nativeTokenPricePrefetch, abortSignal) {
258
+ if (options.unsafeZeroWatchtowerFee)
259
+ return 0n;
260
+ const amount = await (0, Utils_1.throwIfUndefined)(amountPrefetch, "Cannot get swap amount!");
261
+ if (amount === 0n)
262
+ return 0n;
263
+ const totalFeeInNativeToken = await (0, Utils_1.throwIfUndefined)(totalFeeInNativeTokenPrefetch, "Cannot get total fee in native token!");
264
+ const nativeTokenPrice = await nativeTokenPricePrefetch;
265
+ let payoutAmount;
266
+ if (amountData.exactIn) {
267
+ //Convert input amount in BTC to
268
+ const amountInNativeToken = await this._prices.getFromBtcSwapAmount(this.chainIdentifier, amount, this._chain.getNativeCurrencyAddress(), abortSignal, nativeTokenPrice);
269
+ payoutAmount = amountInNativeToken - totalFeeInNativeToken;
270
+ }
271
+ else {
272
+ if (amountData.token === this._chain.getNativeCurrencyAddress()) {
273
+ //Both amounts in same currency
274
+ payoutAmount = amount;
275
+ }
276
+ else {
277
+ //Need to convert both to native currency
278
+ const btcAmount = await this._prices.getToBtcSwapAmount(this.chainIdentifier, amount, amountData.token, abortSignal, await pricePrefetch);
279
+ payoutAmount = await this._prices.getFromBtcSwapAmount(this.chainIdentifier, btcAmount, this._chain.getNativeCurrencyAddress(), abortSignal, nativeTokenPrice);
280
+ }
281
+ }
282
+ this.logger.debug("computeCallerFeeShare(): Caller fee in native token: " + totalFeeInNativeToken.toString(10) + " total payout in native token: " + payoutAmount.toString(10));
283
+ const callerFeeShare = ((totalFeeInNativeToken * 100000n) + payoutAmount - 1n) / payoutAmount; //Make sure to round up here
284
+ if (callerFeeShare < 0n)
285
+ return 0n;
286
+ if (callerFeeShare >= 2n ** 20n)
287
+ return 2n ** 20n - 1n;
288
+ return callerFeeShare;
289
+ }
290
+ /**
291
+ * Verifies response returned from intermediary
292
+ *
293
+ * @param resp Response as returned by the intermediary
294
+ * @param amountData
295
+ * @param lp Intermediary
296
+ * @param options Options as passed to the swap creation function
297
+ * @param callerFeeShare
298
+ * @param maxBitcoinFeeRatePromise Maximum accepted fee rate from the LPs
299
+ * @param bitcoinFeeRatePromise
300
+ * @param abortSignal
301
+ * @private
302
+ * @throws {IntermediaryError} in case the response is invalid
303
+ */
304
+ async verifyReturnedData(resp, amountData, lp, options, callerFeeShare, maxBitcoinFeeRatePromise, bitcoinFeeRatePromise, abortSignal) {
305
+ const btcFeeRate = await (0, Utils_1.throwIfUndefined)(maxBitcoinFeeRatePromise, "Bitcoin fee rate promise failed!");
306
+ abortSignal.throwIfAborted();
307
+ if (btcFeeRate != null && resp.btcFeeRate > btcFeeRate)
308
+ throw new IntermediaryError_1.IntermediaryError(`Required bitcoin fee rate returned from the LP is too high! Maximum accepted: ${btcFeeRate} sats/vB, required by LP: ${resp.btcFeeRate} sats/vB`);
309
+ const lpVersion = lp.getContractVersion(this.chainIdentifier);
310
+ //Vault related
311
+ let vaultScript;
312
+ let vaultAddressType;
313
+ let btcAddressScript;
314
+ let btcAddressType;
315
+ //Ensure valid btc addresses returned
316
+ try {
317
+ vaultScript = (0, BitcoinUtils_1.toOutputScript)(this._options.bitcoinNetwork, resp.vaultBtcAddress);
318
+ vaultAddressType = (0, BitcoinUtils_1.toCoinselectAddressType)(vaultScript);
319
+ btcAddressScript = (0, BitcoinUtils_1.toOutputScript)(this._options.bitcoinNetwork, resp.btcAddress);
320
+ btcAddressType = (0, BitcoinUtils_1.toCoinselectAddressType)(btcAddressScript);
321
+ }
322
+ catch (e) {
323
+ throw new IntermediaryError_1.IntermediaryError("Invalid btc address data returned", e);
324
+ }
325
+ const decodedUtxo = resp.btcUtxo.split(":");
326
+ if (resp.address !== lp.getAddress(this.chainIdentifier) || //Ensure the LP is indeed the vault owner
327
+ resp.vaultId < 0n || //Ensure vaultId is not negative
328
+ vaultScript == null || //Make sure vault script is parsable and of known type
329
+ btcAddressScript == null || //Make sure btc address script is parsable and of known type
330
+ btcAddressType !== exports.REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE || //Constrain the btc address script type
331
+ vaultAddressType !== exports.REQUIRED_SPV_SWAP_VAULT_ADDRESS_TYPE || //Constrain the vault script type
332
+ decodedUtxo.length !== 2 || decodedUtxo[0].length !== 64 || isNaN(parseInt(decodedUtxo[1])) || //Check valid UTXO
333
+ resp.btcFeeRate < 1 || resp.btcFeeRate > 10000 //Sanity check on the returned BTC fee rate
334
+ )
335
+ throw new IntermediaryError_1.IntermediaryError("Invalid vault data returned!");
336
+ //Amounts sanity
337
+ if (resp.btcAmountSwap + resp.btcAmountGas !== resp.btcAmount)
338
+ throw new Error("Btc amount mismatch");
339
+ if (resp.swapFeeBtc + resp.gasSwapFeeBtc !== resp.totalFeeBtc)
340
+ throw new Error("Btc fee mismatch");
341
+ //TODO: For now ensure fees are at 0
342
+ if (resp.callerFeeShare !== callerFeeShare ||
343
+ resp.frontingFeeShare !== 0n ||
344
+ resp.executionFeeShare !== 0n)
345
+ throw new IntermediaryError_1.IntermediaryError("Invalid caller/fronting/execution fee returned");
346
+ //Check expiry
347
+ const timeNowSeconds = Math.floor(Date.now() / 1000);
348
+ if (resp.expiry < timeNowSeconds)
349
+ throw new IntermediaryError_1.IntermediaryError(`Quote already expired, expiry: ${resp.expiry}, systemTime: ${timeNowSeconds}, clockAdjusted: ${Date._now != null}`);
350
+ let utxo = resp.btcUtxo.toLowerCase();
351
+ const [txId, voutStr] = utxo.split(":");
352
+ const abortController = (0, Utils_1.extendAbortController)(abortSignal);
353
+ let [vault, { vaultUtxoValue, btcTx }] = await Promise.all([
354
+ (async () => {
355
+ //Fetch vault data
356
+ let vault;
357
+ try {
358
+ vault = await this._contract(lpVersion).getVaultData(resp.address, resp.vaultId);
359
+ }
360
+ catch (e) {
361
+ this.logger.error("Error getting spv vault (owner: " + resp.address + " vaultId: " + resp.vaultId.toString(10) + "): ", e);
362
+ throw new IntermediaryError_1.IntermediaryError("Spv swap vault not found", e);
363
+ }
364
+ abortController.signal.throwIfAborted();
365
+ //Make sure vault is opened
366
+ if (vault == null || !vault.isOpened())
367
+ throw new IntermediaryError_1.IntermediaryError("Returned spv swap vault is not opened!");
368
+ //Make sure the vault doesn't require insane amount of confirmations
369
+ if (vault.getConfirmations() > this._options.maxConfirmations)
370
+ throw new IntermediaryError_1.IntermediaryError("SPV swap vault needs too many confirmations: " + vault.getConfirmations());
371
+ const tokenData = vault.getTokenData();
372
+ //Amounts - make sure the amounts match
373
+ if (amountData.exactIn) {
374
+ if (!resp.usedUtxoInputCalculation) {
375
+ //Legacy calculation
376
+ if (resp.btcAmount !== amountData.amount)
377
+ throw new IntermediaryError_1.IntermediaryError("Invalid amount returned");
378
+ }
379
+ else {
380
+ //Implies the raw UTXOs were passed for amount derivation
381
+ //Verify the derivation was done correctly
382
+ if (options.sourceWalletUtxos == null)
383
+ throw new IntermediaryError_1.IntermediaryError("Invalid usedUtxoInputCalcuation return value");
384
+ if (bitcoinFeeRatePromise == null)
385
+ throw new Error("bitcoinFeeRatePromise must be passed for UTXO-based input amount calculation checks");
386
+ const walletUtxos = await options.sourceWalletUtxos;
387
+ const bitcoinFeeRate = await (0, Utils_1.throwIfUndefined)(bitcoinFeeRatePromise, "Failed to fetch bitcoin fee rate!");
388
+ const { balance } = BitcoinWallet_1.BitcoinWallet.getSpendableBalance(walletUtxos, Math.max(resp.btcFeeRate, bitcoinFeeRate), this.getDummySwapPsbt(options.gasAmount !== 0n), exports.REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE);
389
+ if (resp.btcAmount !== balance)
390
+ throw new IntermediaryError_1.IntermediaryError(`Invalid amount returned, expected: ${balance.toString(10)}, got: ${resp.btcAmount.toString(10)}`);
391
+ }
392
+ }
393
+ else {
394
+ //Check the difference between amount adjusted due to scaling to raw amount
395
+ const adjustedAmount = amountData.amount / tokenData[0].multiplier * tokenData[0].multiplier;
396
+ const adjustmentPPM = (amountData.amount - adjustedAmount) * 1000000n / amountData.amount;
397
+ if (adjustmentPPM > this._options.maxRawAmountAdjustmentDifferencePPM)
398
+ throw new IntermediaryError_1.IntermediaryError("Invalid amount0 multiplier used, rawAmount diff too high");
399
+ if (resp.total !== adjustedAmount)
400
+ throw new IntermediaryError_1.IntermediaryError("Invalid total returned");
401
+ }
402
+ if (options.gasAmount === 0n) {
403
+ if (resp.totalGas !== 0n)
404
+ throw new IntermediaryError_1.IntermediaryError("Invalid gas total returned");
405
+ }
406
+ else {
407
+ //Check the difference between amount adjusted due to scaling to raw amount
408
+ const adjustedGasAmount = options.gasAmount / tokenData[0].multiplier * tokenData[0].multiplier;
409
+ const adjustmentPPM = (options.gasAmount - adjustedGasAmount) * 1000000n / options.gasAmount;
410
+ if (adjustmentPPM > this._options.maxRawAmountAdjustmentDifferencePPM)
411
+ throw new IntermediaryError_1.IntermediaryError("Invalid amount1 multiplier used, rawAmount diff too high");
412
+ if (resp.totalGas !== adjustedGasAmount)
413
+ throw new IntermediaryError_1.IntermediaryError("Invalid gas total returned");
414
+ }
415
+ return vault;
416
+ })(),
417
+ (async () => {
418
+ //Require the vault UTXO to have at least 1 confirmation
419
+ let btcTx = await this._btcRpc.getTransaction(txId);
420
+ if (btcTx == null)
421
+ throw new IntermediaryError_1.IntermediaryError("Invalid UTXO, doesn't exist (txId)");
422
+ abortController.signal.throwIfAborted();
423
+ if (btcTx.confirmations == null || btcTx.confirmations < 1)
424
+ throw new IntermediaryError_1.IntermediaryError("SPV vault UTXO not confirmed");
425
+ const vout = parseInt(voutStr);
426
+ if (btcTx.outs[vout] == null)
427
+ throw new IntermediaryError_1.IntermediaryError("Invalid UTXO, doesn't exist");
428
+ const vaultUtxoValue = btcTx.outs[vout].value;
429
+ return { btcTx, vaultUtxoValue };
430
+ })(),
431
+ (async () => {
432
+ //Require vault UTXO is unspent
433
+ if (await this._btcRpc.isSpent(utxo))
434
+ throw new IntermediaryError_1.IntermediaryError("Returned spv vault UTXO is already spent", null, true);
435
+ abortController.signal.throwIfAborted();
436
+ })()
437
+ ]).catch(e => {
438
+ abortController.abort(e);
439
+ throw e;
440
+ });
441
+ this.logger.debug("verifyReturnedData(): Vault UTXO: " + vault.getUtxo() + " current utxo: " + utxo);
442
+ //Trace returned utxo back to what's saved on-chain
443
+ let pendingWithdrawals = [];
444
+ while (vault.getUtxo() !== utxo) {
445
+ const [txId, voutStr] = utxo.split(":");
446
+ //Such that 1st tx isn't fetched twice
447
+ if (btcTx.txid !== txId) {
448
+ const _btcTx = await this._btcRpc.getTransaction(txId);
449
+ if (_btcTx == null)
450
+ throw new IntermediaryError_1.IntermediaryError("Invalid ancestor transaction (not found)");
451
+ btcTx = _btcTx;
452
+ }
453
+ const withdrawalData = await this._contract(lpVersion).getWithdrawalData(btcTx);
454
+ abortSignal.throwIfAborted();
455
+ pendingWithdrawals.unshift(withdrawalData);
456
+ utxo = pendingWithdrawals[0].getSpentVaultUtxo();
457
+ this.logger.debug("verifyReturnedData(): Vault UTXO: " + vault.getUtxo() + " current utxo: " + utxo);
458
+ if (pendingWithdrawals.length >= this._options.maxTransactionsDelta)
459
+ throw new IntermediaryError_1.IntermediaryError("BTC <> SC state difference too deep, maximum: " + this._options.maxTransactionsDelta);
460
+ }
461
+ //Verify that the vault has enough balance after processing all pending withdrawals
462
+ let vaultBalances;
463
+ try {
464
+ vaultBalances = vault.calculateStateAfter(pendingWithdrawals);
465
+ }
466
+ catch (e) {
467
+ this.logger.error("Error calculating spv vault balance (owner: " + resp.address + " vaultId: " + resp.vaultId.toString(10) + "): ", e);
468
+ throw new IntermediaryError_1.IntermediaryError("Spv swap vault balance prediction failed", e);
469
+ }
470
+ if (vaultBalances.balances[0].scaledAmount < resp.total)
471
+ throw new IntermediaryError_1.IntermediaryError("SPV swap vault, insufficient balance, required: " + resp.total.toString(10) +
472
+ " has: " + vaultBalances.balances[0].scaledAmount.toString(10));
473
+ if (vaultBalances.balances[1].scaledAmount < resp.totalGas)
474
+ throw new IntermediaryError_1.IntermediaryError("SPV swap vault, insufficient balance, required: " + resp.totalGas.toString(10) +
475
+ " has: " + vaultBalances.balances[1].scaledAmount.toString(10));
476
+ //Also verify that all the withdrawal txns are valid, this is an extra sanity check
477
+ try {
478
+ for (let withdrawal of pendingWithdrawals) {
479
+ await this._contract(lpVersion).checkWithdrawalTx(withdrawal);
480
+ }
481
+ }
482
+ catch (e) {
483
+ this.logger.error("Error calculating spv vault balance (owner: " + resp.address + " vaultId: " + resp.vaultId.toString(10) + "): ", e);
484
+ throw new IntermediaryError_1.IntermediaryError("Spv swap vault balance prediction failed", e);
485
+ }
486
+ abortSignal.throwIfAborted();
487
+ return {
488
+ vault,
489
+ vaultUtxoValue
490
+ };
491
+ }
492
+ async amountPrefetch(amountData, bitcoinFeeRatePromise, walletUtxosPromise, includeGas, abortController) {
493
+ if (amountData.amount != null)
494
+ return amountData.amount;
495
+ try {
496
+ const bitcoinFeeRate = await (0, Utils_1.throwIfUndefined)(bitcoinFeeRatePromise, "Cannot fetch Bitcoin fee rate!");
497
+ if (walletUtxosPromise == null)
498
+ throw new UserError_1.UserError("Cannot use empty amount without passing UTXOs!");
499
+ const walletUtxos = await walletUtxosPromise;
500
+ if (walletUtxos.length === 0)
501
+ throw new UserError_1.UserError("Wallet doesn't have any BTC balance");
502
+ const spendableBalance = await BitcoinWallet_1.BitcoinWallet.getSpendableBalance(walletUtxos, bitcoinFeeRate, this.getDummySwapPsbt(includeGas), exports.REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE);
503
+ return spendableBalance.balance;
504
+ }
505
+ catch (e) {
506
+ abortController.abort(e);
507
+ }
508
+ }
509
+ bitcoinFeeRatePrefetch(options, abortController) {
510
+ let bitcoinFeeRatePromise;
511
+ if (options?.sourceWalletUtxos != null) {
512
+ if (options.bitcoinFeeRate != null) {
513
+ bitcoinFeeRatePromise = options.bitcoinFeeRate.then(value => {
514
+ if (options.maxAllowedBitcoinFeeRate != Infinity && options.maxAllowedBitcoinFeeRate < value)
515
+ throw new Error("Passed `maxAllowedBitcoinFeeRate` cannot be lower than `bitcoinFeeRate`");
516
+ return value;
517
+ });
518
+ }
519
+ else {
520
+ bitcoinFeeRatePromise = this._btcRpc.getFeeRate().then(value => {
521
+ if (options.maxAllowedBitcoinFeeRate != Infinity && value > options.maxAllowedBitcoinFeeRate)
522
+ return options.maxAllowedBitcoinFeeRate;
523
+ return value;
524
+ });
525
+ }
526
+ bitcoinFeeRatePromise = bitcoinFeeRatePromise.catch(e => {
527
+ abortController.abort(e);
528
+ return undefined;
529
+ });
530
+ }
531
+ const maxBitcoinFeeRatePromise = options.maxAllowedBitcoinFeeRate != Infinity
532
+ ? Promise.resolve(options.maxAllowedBitcoinFeeRate)
533
+ : (0, Utils_1.throwIfUndefined)(bitcoinFeeRatePromise ?? options.bitcoinFeeRate ?? this._btcRpc.getFeeRate())
534
+ .then(x => this._options.maxBtcFeeOffset + (x * this._options.maxBtcFeeMultiplier))
535
+ .catch(e => {
536
+ abortController.abort(e);
537
+ return undefined;
538
+ });
539
+ return {
540
+ bitcoinFeeRatePromise,
541
+ maxBitcoinFeeRatePromise
542
+ };
543
+ }
544
+ /**
545
+ * Returns a newly created Bitcoin -> Smart chain swap using the SPV vault (UTXO-controlled vault) swap protocol,
546
+ * with the passed amount. Also allows specifying additional "gas drop" native token that the receipient receives
547
+ * on the destination chain in the `options` argument.
548
+ *
549
+ * @param recipient Recipient address on the destination smart chain
550
+ * @param amountData Amount, token and exact input/output data for to swap
551
+ * @param lps An array of intermediaries (LPs) to get the quotes from
552
+ * @param options Optional additional quote options
553
+ * @param additionalParams Optional additional parameters sent to the LP when creating the swap
554
+ * @param abortSignal Abort signal
555
+ */
556
+ create(recipient, amountData, lps, options, additionalParams, abortSignal) {
557
+ const _options = {
558
+ gasAmount: this.parseGasAmount(options?.gasAmount),
559
+ unsafeZeroWatchtowerFee: options?.unsafeZeroWatchtowerFee ?? false,
560
+ feeSafetyFactor: options?.feeSafetyFactor ?? 1.25,
561
+ maxAllowedBitcoinFeeRate: options?.maxAllowedBitcoinFeeRate ?? options?.maxAllowedNetworkFeeRate ?? Infinity,
562
+ sourceWalletUtxos: options?.sourceWalletUtxos == undefined
563
+ ? undefined
564
+ : options?.sourceWalletUtxos instanceof Promise ? options.sourceWalletUtxos : Promise.resolve(options.sourceWalletUtxos),
565
+ bitcoinFeeRate: options?.bitcoinFeeRate == undefined
566
+ ? undefined
567
+ : options?.bitcoinFeeRate instanceof Promise ? options.bitcoinFeeRate : Promise.resolve(options.bitcoinFeeRate),
568
+ };
569
+ if (_options.gasAmount !== 0n &&
570
+ (this._chain.shouldGetNativeTokenDrop != null
571
+ ? !this._chain.shouldGetNativeTokenDrop(amountData.token)
572
+ : amountData.token === this._chain.getNativeCurrencyAddress()))
573
+ throw new UserError_1.UserError("Cannot specify `gasAmount` for swaps to a native token!");
574
+ if (amountData.amount == null && options?.sourceWalletUtxos == null)
575
+ throw new UserError_1.UserError("Source wallet UTXOs need to be passed when amount is null!");
576
+ if (amountData.amount == null && !amountData.exactIn)
577
+ throw new UserError_1.UserError("Amount can be null only for exactIn swaps!");
578
+ if (amountData.amount != null && options?.sourceWalletUtxos != null)
579
+ throw new UserError_1.UserError("Source wallet UTXOs cannot be passed while specifying an input amount!");
580
+ const lpVersions = Intermediary_1.Intermediary.getContractVersionsForLps(this.chainIdentifier, lps);
581
+ const _abortController = (0, Utils_1.extendAbortController)(abortSignal);
582
+ const pricePrefetchPromise = this.preFetchPrice(amountData, _abortController.signal);
583
+ const usdPricePrefetchPromise = this.preFetchUsdPrice(_abortController.signal);
584
+ const finalizedBlockHeightPrefetchPromise = this.preFetchFinalizedBlockHeight(_abortController);
585
+ const nativeTokenAddress = this._chain.getNativeCurrencyAddress();
586
+ const gasTokenPricePrefetchPromise = _options.gasAmount === 0n ?
587
+ undefined :
588
+ this.preFetchPrice({ token: nativeTokenAddress }, _abortController.signal);
589
+ const callerFeePrefetchPromise = (0, Utils_1.mapArrayToObject)(lpVersions, (contractVersion) => {
590
+ return this.preFetchCallerFeeInNativeToken(amountData, _options, _abortController, contractVersion);
591
+ });
592
+ const { maxBitcoinFeeRatePromise, bitcoinFeeRatePromise } = this.bitcoinFeeRatePrefetch(_options, _abortController);
593
+ const amountPromise = this.amountPrefetch(amountData, maxBitcoinFeeRatePromise, _options.sourceWalletUtxos, _options.gasAmount !== 0n, _abortController);
594
+ return lps.map(lp => {
595
+ return {
596
+ intermediary: lp,
597
+ quote: (0, RetryUtils_1.tryWithRetries)(async () => {
598
+ if (lp.services[SwapType_1.SwapType.SPV_VAULT_FROM_BTC] == null)
599
+ throw new Error("LP service for processing spv vault swaps not found!");
600
+ const version = lp.getContractVersion(this.chainIdentifier);
601
+ const abortController = (0, Utils_1.extendAbortController)(_abortController.signal);
602
+ const callerFeeRatePromise = this.computeCallerFeeShare(amountPromise, callerFeePrefetchPromise[version], amountData, _options, pricePrefetchPromise, gasTokenPricePrefetchPromise, abortController.signal);
603
+ try {
604
+ const resp = await (0, RetryUtils_1.tryWithRetries)(async (retryCount) => {
605
+ return await this._lpApi.prepareSpvFromBTC(this.chainIdentifier, lp.url, {
606
+ address: recipient,
607
+ amount: (0, Utils_1.throwIfUndefined)(amountPromise, "Failed to compute swap amount"),
608
+ token: amountData.token.toString(),
609
+ exactOut: !amountData.exactIn,
610
+ gasToken: nativeTokenAddress,
611
+ gasAmount: _options.gasAmount,
612
+ callerFeeRate: (0, Utils_1.throwIfUndefined)(callerFeeRatePromise, "Caller fee prefetch failed!"),
613
+ frontingFeeRate: 0n,
614
+ stickyAddress: options?.stickyAddress,
615
+ amountUtxos: _options.sourceWalletUtxos != null
616
+ ? _options.sourceWalletUtxos.then(utxos => {
617
+ if (utxos.length === 0)
618
+ return undefined;
619
+ return utxos.map(utxo => ({
620
+ value: utxo.value,
621
+ vSize: utils_2.utils.inputBytes({ type: utxo.type }),
622
+ cpfp: utxo.cpfp == null ? undefined : { effectiveVSize: utxo.cpfp?.txVsize, effectiveFeeRate: utxo.cpfp?.txEffectiveFeeRate }
623
+ }));
624
+ })
625
+ : undefined,
626
+ amountFeeRate: bitcoinFeeRatePromise,
627
+ additionalParams
628
+ }, this._options.postRequestTimeout, abortController.signal, retryCount > 0 ? false : undefined);
629
+ }, undefined, e => e instanceof RequestError_1.RequestError, abortController.signal);
630
+ this.logger.debug("create(" + lp.url + "): LP response: ", resp);
631
+ const callerFeeShare = await callerFeeRatePromise;
632
+ const amount = await (0, Utils_1.throwIfUndefined)(amountPromise);
633
+ const [pricingInfo, gasPricingInfo, { vault, vaultUtxoValue }] = await Promise.all([
634
+ this.verifyReturnedPrice(lp.services[SwapType_1.SwapType.SPV_VAULT_FROM_BTC], false, resp.btcAmountSwap, resp.total * (100000n + callerFeeShare) / 100000n, amountData.token, { swapFeeBtc: resp.swapFeeBtc }, pricePrefetchPromise, usdPricePrefetchPromise, abortController.signal),
635
+ _options.gasAmount === 0n ? Promise.resolve(undefined) : this.verifyReturnedPrice({ ...lp.services[SwapType_1.SwapType.SPV_VAULT_FROM_BTC], swapBaseFee: 0 }, //Base fee should be charged only on the amount, not on gas
636
+ false, resp.btcAmountGas, resp.totalGas * (100000n + callerFeeShare) / 100000n, nativeTokenAddress, { swapFeeBtc: resp.gasSwapFeeBtc }, gasTokenPricePrefetchPromise, usdPricePrefetchPromise, abortController.signal),
637
+ this.verifyReturnedData(resp, { ...amountData, amount }, lp, _options, callerFeeShare, maxBitcoinFeeRatePromise, bitcoinFeeRatePromise, abortController.signal)
638
+ ]);
639
+ let minimumBtcFeeRate = resp.btcFeeRate;
640
+ if (bitcoinFeeRatePromise != null)
641
+ minimumBtcFeeRate = Math.max(minimumBtcFeeRate, await (0, Utils_1.throwIfUndefined)(bitcoinFeeRatePromise));
642
+ const swapInit = {
643
+ pricingInfo,
644
+ url: lp.url,
645
+ expiry: resp.expiry * 1000,
646
+ swapFee: resp.swapFee,
647
+ swapFeeBtc: resp.swapFeeBtc,
648
+ exactIn: amountData.exactIn ?? true,
649
+ quoteId: resp.quoteId,
650
+ recipient,
651
+ vaultOwner: resp.address,
652
+ vaultId: resp.vaultId,
653
+ vaultRequiredConfirmations: vault.getConfirmations(),
654
+ vaultTokenMultipliers: vault.getTokenData().map(val => val.multiplier),
655
+ vaultBtcAddress: resp.vaultBtcAddress,
656
+ vaultUtxo: resp.btcUtxo,
657
+ vaultUtxoValue: BigInt(vaultUtxoValue),
658
+ btcDestinationAddress: resp.btcAddress,
659
+ btcAmount: resp.btcAmount,
660
+ btcAmountSwap: resp.btcAmountSwap,
661
+ btcAmountGas: resp.btcAmountGas,
662
+ minimumBtcFeeRate,
663
+ outputTotalSwap: resp.total,
664
+ outputSwapToken: amountData.token,
665
+ outputTotalGas: resp.totalGas,
666
+ outputGasToken: nativeTokenAddress,
667
+ gasSwapFeeBtc: resp.gasSwapFeeBtc,
668
+ gasSwapFee: resp.gasSwapFee,
669
+ gasPricingInfo,
670
+ callerFeeShare: resp.callerFeeShare,
671
+ frontingFeeShare: resp.frontingFeeShare,
672
+ executionFeeShare: resp.executionFeeShare,
673
+ genesisSmartChainBlockHeight: await (0, Utils_1.throwIfUndefined)(finalizedBlockHeightPrefetchPromise, "Network finalized blockheight pre-fetch failed!"),
674
+ contractVersion: version
675
+ };
676
+ const quote = new SpvFromBTCSwap_1.SpvFromBTCSwap(this, swapInit);
677
+ return quote;
678
+ }
679
+ catch (e) {
680
+ if (e instanceof RequestError_1.OutOfBoundsError) {
681
+ const amountResult = await amountPromise.catch(() => undefined);
682
+ if (_options.sourceWalletUtxos != null && amountResult != null && amountResult <= 0n) {
683
+ e = new UserError_1.UserError("Wallet doesn't have enough BTC balance to cover transaction fees");
684
+ }
685
+ }
686
+ abortController.abort(e);
687
+ throw e;
688
+ }
689
+ }, undefined, err => !(err instanceof IntermediaryError_1.IntermediaryError && err.recoverable), _abortController.signal)
690
+ };
691
+ });
692
+ }
693
+ /**
694
+ * Recovers an SPV vault (UTXO-controlled vault) based swap from smart chain on-chain data
695
+ *
696
+ * @param state State of the spv vault withdrawal recovered from on-chain data
697
+ * @param vault SPV vault processing the swap
698
+ * @param lp Intermediary (LP) used as a counterparty for the swap
699
+ */
700
+ async recoverFromState(state, contractVersion, vault, lp) {
701
+ //Get the vault
702
+ vault ??= await this._contract(contractVersion).getVaultData(state.owner, state.vaultId);
703
+ if (vault == null)
704
+ return null;
705
+ if (state.btcTxId == null)
706
+ return null;
707
+ const btcTx = await this._btcRpc.getTransaction(state.btcTxId);
708
+ if (btcTx == null)
709
+ return null;
710
+ const withdrawalData = await this._contract(contractVersion).getWithdrawalData(btcTx)
711
+ .catch(e => {
712
+ this.logger.warn(`Error parsing withdrawal data for tx ${btcTx.txid}: `, e);
713
+ return null;
714
+ });
715
+ if (withdrawalData == null)
716
+ return null;
717
+ const vaultTokens = vault.getTokenData();
718
+ const withdrawalDataOutputs = withdrawalData.getTotalOutput();
719
+ const txBlock = await state.getTxBlock?.();
720
+ const swapInit = {
721
+ pricingInfo: {
722
+ isValid: true,
723
+ satsBaseFee: 0n,
724
+ swapPriceUSatPerToken: 100000000000000n,
725
+ realPriceUSatPerToken: 100000000000000n,
726
+ differencePPM: 0n,
727
+ feePPM: 0n,
728
+ },
729
+ url: lp?.url,
730
+ expiry: 0,
731
+ swapFee: 0n,
732
+ swapFeeBtc: 0n,
733
+ exactIn: true,
734
+ //Use bitcoin tx id as quote id, even though this is not strictly correct as this
735
+ // is an off-chain identifier presented by the LP that cannot be recovered from on-chain
736
+ // data
737
+ quoteId: btcTx.txid,
738
+ recipient: state.recipient,
739
+ vaultOwner: state.owner,
740
+ vaultId: state.vaultId,
741
+ vaultRequiredConfirmations: vault.getConfirmations(),
742
+ vaultTokenMultipliers: vault.getTokenData().map(val => val.multiplier),
743
+ vaultBtcAddress: (0, BitcoinUtils_1.fromOutputScript)(this._options.bitcoinNetwork, withdrawalData.getNewVaultScript().toString("hex")),
744
+ vaultUtxo: withdrawalData.getSpentVaultUtxo(),
745
+ vaultUtxoValue: BigInt(withdrawalData.getNewVaultBtcAmount()),
746
+ btcDestinationAddress: (0, BitcoinUtils_1.fromOutputScript)(this._options.bitcoinNetwork, btcTx.outs[2].scriptPubKey.hex),
747
+ btcAmount: BigInt(btcTx.outs[2].value),
748
+ btcAmountSwap: BigInt(btcTx.outs[2].value),
749
+ btcAmountGas: 0n,
750
+ minimumBtcFeeRate: 0,
751
+ outputTotalSwap: withdrawalDataOutputs[0] * vaultTokens[0].multiplier,
752
+ outputSwapToken: vaultTokens[0].token,
753
+ outputTotalGas: withdrawalDataOutputs[1] * vaultTokens[1].multiplier,
754
+ outputGasToken: vaultTokens[1].token,
755
+ gasSwapFeeBtc: 0n,
756
+ gasSwapFee: 0n,
757
+ gasPricingInfo: {
758
+ isValid: true,
759
+ satsBaseFee: 0n,
760
+ swapPriceUSatPerToken: 100000000000000n,
761
+ realPriceUSatPerToken: 100000000000000n,
762
+ differencePPM: 0n,
763
+ feePPM: 0n,
764
+ },
765
+ callerFeeShare: withdrawalData.callerFeeRate,
766
+ frontingFeeShare: withdrawalData.frontingFeeRate,
767
+ executionFeeShare: withdrawalData.executionFeeRate,
768
+ genesisSmartChainBlockHeight: txBlock?.blockHeight ?? 0,
769
+ contractVersion
770
+ };
771
+ const quote = new SpvFromBTCSwap_1.SpvFromBTCSwap(this, swapInit);
772
+ quote._data = withdrawalData;
773
+ if (txBlock != null) {
774
+ quote.createdAt = txBlock.blockTime * 1000;
775
+ }
776
+ else if (btcTx.blockhash == null) {
777
+ quote.createdAt = Date.now();
778
+ }
779
+ else {
780
+ const blockHeader = await this._btcRpc.getBlockHeader(btcTx.blockhash);
781
+ quote.createdAt = blockHeader == null ? Date.now() : blockHeader.getTimestamp() * 1000;
782
+ }
783
+ quote._setInitiated();
784
+ if (btcTx.inputAddresses != null)
785
+ quote._senderAddress = btcTx.inputAddresses[1];
786
+ if (state.type === base_1.SpvWithdrawalStateType.FRONTED) {
787
+ quote._frontTxId = state.txId;
788
+ quote._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.FRONTED;
789
+ }
790
+ else {
791
+ quote._claimTxId = state.txId;
792
+ quote._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLAIMED;
793
+ }
794
+ await quote._save();
795
+ return quote;
796
+ }
797
+ /**
798
+ * Returns a random dummy PSBT that can be used for fee estimation, the last output (the LP output) is omitted
799
+ * to allow for coinselection algorithm to determine maximum sendable amount there
800
+ *
801
+ * @param includeGasToken Whether to return the PSBT also with the gas token amount (increases the vSize by 8)
802
+ */
803
+ getDummySwapPsbt(includeGasToken = false) {
804
+ //Construct dummy swap psbt
805
+ const psbt = new btc_signer_1.Transaction({
806
+ allowUnknownInputs: true,
807
+ allowLegacyWitnessUtxo: true,
808
+ allowUnknownOutputs: true
809
+ });
810
+ const randomVaultOutScript = (0, BitcoinUtils_1.getDummyOutputScript)(exports.REQUIRED_SPV_SWAP_VAULT_ADDRESS_TYPE);
811
+ psbt.addInput({
812
+ txid: (0, Utils_1.randomBytes)(32),
813
+ index: 0,
814
+ witnessUtxo: {
815
+ script: randomVaultOutScript,
816
+ amount: 600n
817
+ }
818
+ });
819
+ psbt.addOutput({
820
+ script: randomVaultOutScript,
821
+ amount: 600n
822
+ });
823
+ let longestOpReturnData = undefined;
824
+ for (let contractVersion in this.versionedContracts) {
825
+ if (this.versionedContracts[contractVersion].spvVaultContract == null)
826
+ continue;
827
+ const opReturnData = this._contract(contractVersion).toOpReturnData(this._chain.randomAddress(), includeGasToken ? [0xffffffffffffffffn, 0xffffffffffffffffn] : [0xffffffffffffffffn]);
828
+ if (longestOpReturnData == null || longestOpReturnData.length < opReturnData.length)
829
+ longestOpReturnData = opReturnData;
830
+ }
831
+ if (longestOpReturnData == null)
832
+ throw new Error(`No contract version supporting the Spv Vault BTC -> ${this.chainIdentifier} swaps found!`);
833
+ psbt.addOutput({
834
+ script: Buffer.concat([
835
+ longestOpReturnData.length <= 75 ? Buffer.from([0x6a, longestOpReturnData.length]) : Buffer.from([0x6a, 0x4c, longestOpReturnData.length]),
836
+ longestOpReturnData
837
+ ]),
838
+ amount: 0n
839
+ });
840
+ return psbt;
841
+ }
842
+ /**
843
+ * @inheritDoc
844
+ * @internal
845
+ */
846
+ async _checkPastSwaps(pastSwaps) {
847
+ const changedSwaps = new Set();
848
+ const removeSwaps = [];
849
+ const broadcastedOrConfirmedSwaps = {};
850
+ for (let pastSwap of pastSwaps) {
851
+ let changed = false;
852
+ if (pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED ||
853
+ pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED ||
854
+ pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED ||
855
+ pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED ||
856
+ pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED ||
857
+ pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
858
+ //Check BTC transaction
859
+ if (await pastSwap._syncStateFromBitcoin(false))
860
+ changed ||= true;
861
+ }
862
+ if (pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.CREATED ||
863
+ pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED ||
864
+ pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED) {
865
+ if (await pastSwap._verifyQuoteDefinitelyExpired()) {
866
+ if (pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.CREATED) {
867
+ pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_EXPIRED;
868
+ }
869
+ else {
870
+ pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED;
871
+ }
872
+ changed ||= true;
873
+ }
874
+ }
875
+ if (pastSwap.isQuoteExpired()) {
876
+ removeSwaps.push(pastSwap);
877
+ continue;
878
+ }
879
+ if (changed)
880
+ changedSwaps.add(pastSwap);
881
+ if (pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED || pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
882
+ if (pastSwap._data != null)
883
+ (broadcastedOrConfirmedSwaps[pastSwap._contractVersion ?? "v1"] ??= []).push(pastSwap);
884
+ }
885
+ }
886
+ for (let contractVersion in broadcastedOrConfirmedSwaps) {
887
+ if (this.versionedContracts[contractVersion] == null) {
888
+ this.logger.warn(`_checkPastSwaps(): No contract was found for ${this.chainIdentifier} version ${contractVersion}! Skipping these swaps!`);
889
+ continue;
890
+ }
891
+ const _broadcastedOrConfirmedSwaps = broadcastedOrConfirmedSwaps[contractVersion];
892
+ const checkWithdrawalStateSwaps = [];
893
+ const _fronts = await this._contract(contractVersion).getFronterAddresses(_broadcastedOrConfirmedSwaps.map(val => ({
894
+ ...val.getSpvVaultData(),
895
+ withdrawal: val._data
896
+ })));
897
+ const _vaultUtxos = await this._contract(contractVersion).getVaultLatestUtxos(_broadcastedOrConfirmedSwaps.map(val => val.getSpvVaultData()));
898
+ for (const pastSwap of _broadcastedOrConfirmedSwaps) {
899
+ const fronterAddress = _fronts[pastSwap._data.getTxId()];
900
+ const vault = pastSwap.getSpvVaultData();
901
+ const latestVaultUtxo = _vaultUtxos[vault.owner]?.[vault.vaultId.toString(10)];
902
+ if (fronterAddress === undefined)
903
+ this.logger.warn(`_checkPastSwaps(): No fronter address returned for ${pastSwap._data.getTxId()}`);
904
+ if (latestVaultUtxo === undefined)
905
+ this.logger.warn(`_checkPastSwaps(): No last vault utxo returned for ${pastSwap._data.getTxId()}`);
906
+ if (await pastSwap._shouldCheckWithdrawalState(fronterAddress, latestVaultUtxo))
907
+ checkWithdrawalStateSwaps.push(pastSwap);
908
+ }
909
+ const withdrawalStates = await this._contract(contractVersion).getWithdrawalStates(checkWithdrawalStateSwaps.map(val => ({
910
+ withdrawal: val._data,
911
+ scStartBlockheight: val._genesisSmartChainBlockHeight
912
+ })));
913
+ for (const pastSwap of checkWithdrawalStateSwaps) {
914
+ const status = withdrawalStates[pastSwap._data.getTxId()];
915
+ if (status == null) {
916
+ this.logger.warn(`_checkPastSwaps(): No withdrawal state returned for ${pastSwap._data.getTxId()}`);
917
+ continue;
918
+ }
919
+ this.logger.debug("syncStateFromChain(): status of " + pastSwap._data.btcTx.txid, status?.type);
920
+ let changed = false;
921
+ switch (status.type) {
922
+ case base_1.SpvWithdrawalStateType.FRONTED:
923
+ pastSwap._frontTxId = status.txId;
924
+ pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.FRONTED;
925
+ changed ||= true;
926
+ break;
927
+ case base_1.SpvWithdrawalStateType.CLAIMED:
928
+ pastSwap._claimTxId = status.txId;
929
+ pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLAIMED;
930
+ changed ||= true;
931
+ break;
932
+ case base_1.SpvWithdrawalStateType.CLOSED:
933
+ pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLOSED;
934
+ changed ||= true;
935
+ break;
936
+ }
937
+ if (changed)
938
+ changedSwaps.add(pastSwap);
939
+ }
940
+ }
941
+ return {
942
+ changedSwaps: Array.from(changedSwaps),
943
+ removeSwaps
944
+ };
945
+ }
946
+ }
947
+ exports.SpvFromBTCWrapper = SpvFromBTCWrapper;