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