@atomiqlabs/sdk 8.9.1 → 8.9.2
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 +765 -770
- package/dist/swapper/Swapper.js +1749 -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 +2557 -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,1463 +1,1463 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FromBTCSwap = exports.isFromBTCSwapInit = exports.FromBTCSwapState = void 0;
|
|
4
|
-
const IFromBTCSelfInitSwap_1 = require("../IFromBTCSelfInitSwap");
|
|
5
|
-
const SwapType_1 = require("../../../../enums/SwapType");
|
|
6
|
-
const base_1 = require("@atomiqlabs/base");
|
|
7
|
-
const buffer_1 = require("buffer");
|
|
8
|
-
const Utils_1 = require("../../../../utils/Utils");
|
|
9
|
-
const BitcoinUtils_1 = require("../../../../utils/BitcoinUtils");
|
|
10
|
-
const IBitcoinWallet_1 = require("../../../../bitcoin/wallet/IBitcoinWallet");
|
|
11
|
-
const btc_signer_1 = require("@scure/btc-signer");
|
|
12
|
-
const SingleAddressBitcoinWallet_1 = require("../../../../bitcoin/wallet/SingleAddressBitcoinWallet");
|
|
13
|
-
const IEscrowSelfInitSwap_1 = require("../../IEscrowSelfInitSwap");
|
|
14
|
-
const TokenAmount_1 = require("../../../../types/TokenAmount");
|
|
15
|
-
const Token_1 = require("../../../../types/Token");
|
|
16
|
-
const Logger_1 = require("../../../../utils/Logger");
|
|
17
|
-
const BitcoinWalletUtils_1 = require("../../../../utils/BitcoinWalletUtils");
|
|
18
|
-
/**
|
|
19
|
-
* State enum for legacy escrow based Bitcoin -> Smart chain swaps.
|
|
20
|
-
*
|
|
21
|
-
* @category Swaps/Legacy/Bitcoin → Smart chain
|
|
22
|
-
*/
|
|
23
|
-
var FromBTCSwapState;
|
|
24
|
-
(function (FromBTCSwapState) {
|
|
25
|
-
/**
|
|
26
|
-
* Bitcoin swap address has expired and the intermediary (LP) has already refunded
|
|
27
|
-
* its funds. No BTC should be sent anymore!
|
|
28
|
-
*/
|
|
29
|
-
FromBTCSwapState[FromBTCSwapState["FAILED"] = -4] = "FAILED";
|
|
30
|
-
/**
|
|
31
|
-
* Bitcoin swap address has expired, user should not send any BTC anymore! Though
|
|
32
|
-
* the intermediary (LP) hasn't refunded yet. So if there is a transaction already
|
|
33
|
-
* in-flight the swap might still succeed.
|
|
34
|
-
*/
|
|
35
|
-
FromBTCSwapState[FromBTCSwapState["EXPIRED"] = -3] = "EXPIRED";
|
|
36
|
-
/**
|
|
37
|
-
* Swap has expired for good and there is no way how it can be executed anymore
|
|
38
|
-
*/
|
|
39
|
-
FromBTCSwapState[FromBTCSwapState["QUOTE_EXPIRED"] = -2] = "QUOTE_EXPIRED";
|
|
40
|
-
/**
|
|
41
|
-
* A swap is almost expired, and it should be presented to the user as expired, though
|
|
42
|
-
* there is still a chance that it will be processed
|
|
43
|
-
*/
|
|
44
|
-
FromBTCSwapState[FromBTCSwapState["QUOTE_SOFT_EXPIRED"] = -1] = "QUOTE_SOFT_EXPIRED";
|
|
45
|
-
/**
|
|
46
|
-
* Swap quote was created, use the {@link FromBTCSwap.commit} or {@link FromBTCSwap.txsCommit} functions
|
|
47
|
-
* to initiate it by creating the swap escrow on the destination smart chain
|
|
48
|
-
*/
|
|
49
|
-
FromBTCSwapState[FromBTCSwapState["PR_CREATED"] = 0] = "PR_CREATED";
|
|
50
|
-
/**
|
|
51
|
-
* Swap escrow was initiated (committed) on the destination chain, user can send the BTC to the
|
|
52
|
-
* swap address with the {@link FromBTCSwap.getFundedPsbt}, {@link FromBTCSwap.getAddress} or
|
|
53
|
-
* {@link FromBTCSwap.getHyperlink} functions.
|
|
54
|
-
*/
|
|
55
|
-
FromBTCSwapState[FromBTCSwapState["CLAIM_COMMITED"] = 1] = "CLAIM_COMMITED";
|
|
56
|
-
/**
|
|
57
|
-
* Input bitcoin transaction was confirmed, wait for automatic settlement by the watchtowers
|
|
58
|
-
* using the {@link FromBTCSwap.waitTillClaimed} function or settle manually using the {@link FromBTCSwap.claim}
|
|
59
|
-
* or {@link FromBTCSwap.txsClaim} function.
|
|
60
|
-
*/
|
|
61
|
-
FromBTCSwapState[FromBTCSwapState["BTC_TX_CONFIRMED"] = 2] = "BTC_TX_CONFIRMED";
|
|
62
|
-
/**
|
|
63
|
-
* Swap successfully settled and funds received on the destination chain
|
|
64
|
-
*/
|
|
65
|
-
FromBTCSwapState[FromBTCSwapState["CLAIM_CLAIMED"] = 3] = "CLAIM_CLAIMED";
|
|
66
|
-
})(FromBTCSwapState = exports.FromBTCSwapState || (exports.FromBTCSwapState = {}));
|
|
67
|
-
const FromBTCSwapStateDescription = {
|
|
68
|
-
[FromBTCSwapState.FAILED]: "Bitcoin swap address has expired and the intermediary (LP) has already refunded its funds. No BTC should be sent anymore!",
|
|
69
|
-
[FromBTCSwapState.EXPIRED]: "Bitcoin swap address has expired, user should not send any BTC anymore! Though the intermediary (LP) hasn't refunded yet. So if there is a transaction already in-flight the swap might still succeed.",
|
|
70
|
-
[FromBTCSwapState.QUOTE_EXPIRED]: "Swap has expired for good and there is no way how it can be executed anymore",
|
|
71
|
-
[FromBTCSwapState.QUOTE_SOFT_EXPIRED]: "The swap is expired, though there is still a chance that it will be processed",
|
|
72
|
-
[FromBTCSwapState.PR_CREATED]: "Swap quote was created, initiate it by creating the swap escrow on the destination smart chain",
|
|
73
|
-
[FromBTCSwapState.CLAIM_COMMITED]: "Swap escrow was initiated (committed) on the destination chain, user can send the BTC to the Bitcoin swap address.",
|
|
74
|
-
[FromBTCSwapState.BTC_TX_CONFIRMED]: "Input bitcoin transaction was confirmed, wait for automatic settlement by the watchtower or settle manually.",
|
|
75
|
-
[FromBTCSwapState.CLAIM_CLAIMED]: "Swap successfully settled and funds received on the destination chain"
|
|
76
|
-
};
|
|
77
|
-
function isFromBTCSwapInit(obj) {
|
|
78
|
-
return typeof (obj.data) === "object" &&
|
|
79
|
-
(obj.address == null || typeof (obj.address) === "string") &&
|
|
80
|
-
(obj.amount == null || typeof (obj.amount) === "bigint") &&
|
|
81
|
-
(obj.requiredConfirmations == null || typeof (obj.requiredConfirmations) === "number") &&
|
|
82
|
-
(0, IEscrowSelfInitSwap_1.isIEscrowSelfInitSwapInit)(obj);
|
|
83
|
-
}
|
|
84
|
-
exports.isFromBTCSwapInit = isFromBTCSwapInit;
|
|
85
|
-
/**
|
|
86
|
-
* Legacy escrow (PrTLC) based swap for Bitcoin -> Smart chains, requires manual initiation
|
|
87
|
-
* of the swap escrow on the destination chain.
|
|
88
|
-
*
|
|
89
|
-
* @category Swaps/Legacy/Bitcoin → Smart chain
|
|
90
|
-
*/
|
|
91
|
-
class FromBTCSwap extends IFromBTCSelfInitSwap_1.IFromBTCSelfInitSwap {
|
|
92
|
-
constructor(wrapper, initOrObject) {
|
|
93
|
-
if (isFromBTCSwapInit(initOrObject) && initOrObject.url != null)
|
|
94
|
-
initOrObject.url += "/frombtc";
|
|
95
|
-
super(wrapper, initOrObject);
|
|
96
|
-
this.TYPE = SwapType_1.SwapType.FROM_BTC;
|
|
97
|
-
/**
|
|
98
|
-
* @internal
|
|
99
|
-
*/
|
|
100
|
-
this.swapStateName = (state) => FromBTCSwapState[state];
|
|
101
|
-
/**
|
|
102
|
-
* @internal
|
|
103
|
-
*/
|
|
104
|
-
this.swapStateDescription = FromBTCSwapStateDescription;
|
|
105
|
-
/**
|
|
106
|
-
* @internal
|
|
107
|
-
*/
|
|
108
|
-
this.inputToken = Token_1.BitcoinTokens.BTC;
|
|
109
|
-
if (isFromBTCSwapInit(initOrObject)) {
|
|
110
|
-
this._state = FromBTCSwapState.PR_CREATED;
|
|
111
|
-
this._data = initOrObject.data;
|
|
112
|
-
this.feeRate = initOrObject.feeRate;
|
|
113
|
-
this.address = initOrObject.address;
|
|
114
|
-
this.amount = initOrObject.amount;
|
|
115
|
-
this.requiredConfirmations = initOrObject.requiredConfirmations;
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
this.address = initOrObject.address;
|
|
119
|
-
this.amount = (0, Utils_1.toBigInt)(initOrObject.amount);
|
|
120
|
-
this.senderAddress = initOrObject.senderAddress;
|
|
121
|
-
this.txId = initOrObject.txId;
|
|
122
|
-
this.vout = initOrObject.vout;
|
|
123
|
-
this.requiredConfirmations = initOrObject.requiredConfirmations ?? this._data.getConfirmationsHint();
|
|
124
|
-
this.btcTxConfirmedAt = initOrObject.btcTxConfirmedAt;
|
|
125
|
-
}
|
|
126
|
-
this.tryRecomputeSwapPrice();
|
|
127
|
-
this.logger = (0, Logger_1.getLogger)("FromBTC(" + this.getIdentifierHashString() + "): ");
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* @inheritDoc
|
|
131
|
-
* @internal
|
|
132
|
-
*/
|
|
133
|
-
getSwapData() {
|
|
134
|
-
return this._data;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* @inheritDoc
|
|
138
|
-
* @internal
|
|
139
|
-
*/
|
|
140
|
-
upgradeVersion() {
|
|
141
|
-
if (this.version == null) {
|
|
142
|
-
switch (this._state) {
|
|
143
|
-
case -2:
|
|
144
|
-
this._state = FromBTCSwapState.FAILED;
|
|
145
|
-
break;
|
|
146
|
-
case -1:
|
|
147
|
-
this._state = FromBTCSwapState.QUOTE_EXPIRED;
|
|
148
|
-
break;
|
|
149
|
-
case 0:
|
|
150
|
-
this._state = FromBTCSwapState.PR_CREATED;
|
|
151
|
-
break;
|
|
152
|
-
case 1:
|
|
153
|
-
this._state = FromBTCSwapState.CLAIM_COMMITED;
|
|
154
|
-
break;
|
|
155
|
-
case 2:
|
|
156
|
-
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
157
|
-
break;
|
|
158
|
-
case 3:
|
|
159
|
-
this._state = FromBTCSwapState.CLAIM_CLAIMED;
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
162
|
-
this.version = 1;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
//////////////////////////////
|
|
166
|
-
//// Getters & utils
|
|
167
|
-
/**
|
|
168
|
-
* Returns bitcoin address where the on-chain BTC should be sent to
|
|
169
|
-
*/
|
|
170
|
-
getAddress() {
|
|
171
|
-
if (this._state === FromBTCSwapState.PR_CREATED)
|
|
172
|
-
throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
|
|
173
|
-
return this.address ?? "";
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Unsafe bitcoin hyperlink getter, returns the address even before the swap is committed!
|
|
177
|
-
*
|
|
178
|
-
* @private
|
|
179
|
-
*/
|
|
180
|
-
_getHyperlink() {
|
|
181
|
-
return this.address == null || this.amount == null ? "" : "bitcoin:" + this.address + "?amount=" + encodeURIComponent((Number(this.amount) / 100000000).toString(10));
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* @inheritDoc
|
|
185
|
-
*/
|
|
186
|
-
getHyperlink() {
|
|
187
|
-
if (this._state === FromBTCSwapState.PR_CREATED)
|
|
188
|
-
throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
|
|
189
|
-
return this._getHyperlink();
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* @inheritDoc
|
|
193
|
-
*/
|
|
194
|
-
getInputAddress() {
|
|
195
|
-
return this.senderAddress ?? null;
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* @inheritDoc
|
|
199
|
-
*/
|
|
200
|
-
getInputTxId() {
|
|
201
|
-
return this.txId ?? null;
|
|
202
|
-
}
|
|
203
|
-
async _setSubmittedBitcoinTx(txId, psbt) {
|
|
204
|
-
let changed = false;
|
|
205
|
-
if (this.txId !== txId) {
|
|
206
|
-
this.txId = txId;
|
|
207
|
-
changed = true;
|
|
208
|
-
}
|
|
209
|
-
const submittedVout = this.address == null || this.amount == null || psbt == null
|
|
210
|
-
? undefined
|
|
211
|
-
: (0, BitcoinUtils_1.getVoutIndex)(psbt, this.wrapper._options.bitcoinNetwork, this.address, this.amount);
|
|
212
|
-
if (submittedVout != null && this.vout !== submittedVout) {
|
|
213
|
-
this.vout = submittedVout;
|
|
214
|
-
changed = true;
|
|
215
|
-
}
|
|
216
|
-
const submittedSenderAddress = psbt == null
|
|
217
|
-
? undefined
|
|
218
|
-
: (0, BitcoinUtils_1.getSenderAddress)(psbt, this.wrapper._options.bitcoinNetwork);
|
|
219
|
-
if (submittedSenderAddress != null && this.senderAddress !== submittedSenderAddress) {
|
|
220
|
-
this.senderAddress = submittedSenderAddress;
|
|
221
|
-
changed = true;
|
|
222
|
-
}
|
|
223
|
-
if (changed)
|
|
224
|
-
await this._saveAndEmit();
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Returns timeout time (in UNIX milliseconds) when the on-chain address will expire and no funds should be sent
|
|
228
|
-
* to that address anymore
|
|
229
|
-
*/
|
|
230
|
-
getTimeoutTime() {
|
|
231
|
-
return Number(this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations ?? 6)) * 1000;
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* @inheritDoc
|
|
235
|
-
*/
|
|
236
|
-
requiresAction() {
|
|
237
|
-
return this.isClaimable() || (this._state === FromBTCSwapState.CLAIM_COMMITED && this.getTimeoutTime() > Date.now() && this.txId == null);
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* @inheritDoc
|
|
241
|
-
*/
|
|
242
|
-
isFinished() {
|
|
243
|
-
return this._state === FromBTCSwapState.CLAIM_CLAIMED || this._state === FromBTCSwapState.QUOTE_EXPIRED || this._state === FromBTCSwapState.FAILED;
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* @inheritDoc
|
|
247
|
-
*/
|
|
248
|
-
isClaimable() {
|
|
249
|
-
return this._state === FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
250
|
-
}
|
|
251
|
-
/**
|
|
252
|
-
* @inheritDoc
|
|
253
|
-
*/
|
|
254
|
-
isSuccessful() {
|
|
255
|
-
return this._state === FromBTCSwapState.CLAIM_CLAIMED;
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* @inheritDoc
|
|
259
|
-
*/
|
|
260
|
-
isFailed() {
|
|
261
|
-
return this._state === FromBTCSwapState.FAILED || this._state === FromBTCSwapState.EXPIRED;
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* @inheritDoc
|
|
265
|
-
*/
|
|
266
|
-
isInProgress() {
|
|
267
|
-
return this._state === FromBTCSwapState.CLAIM_COMMITED ||
|
|
268
|
-
this._state === FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* @inheritDoc
|
|
272
|
-
*/
|
|
273
|
-
isQuoteExpired() {
|
|
274
|
-
return this._state === FromBTCSwapState.QUOTE_EXPIRED;
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* @inheritDoc
|
|
278
|
-
*/
|
|
279
|
-
isQuoteSoftExpired() {
|
|
280
|
-
return this._state === FromBTCSwapState.QUOTE_EXPIRED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED;
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* @inheritDoc
|
|
284
|
-
* @internal
|
|
285
|
-
*/
|
|
286
|
-
canCommit(skipQuoteExpiryChecks) {
|
|
287
|
-
if (this._state !== FromBTCSwapState.PR_CREATED && (!skipQuoteExpiryChecks || this._state !== FromBTCSwapState.QUOTE_SOFT_EXPIRED))
|
|
288
|
-
return false;
|
|
289
|
-
if (this.requiredConfirmations == null)
|
|
290
|
-
return false;
|
|
291
|
-
const expiry = this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations);
|
|
292
|
-
const currentTimestamp = BigInt(Math.floor(Date.now() / 1000));
|
|
293
|
-
return (expiry - currentTimestamp) >= this.wrapper._options.minSendWindow;
|
|
294
|
-
}
|
|
295
|
-
//////////////////////////////
|
|
296
|
-
//// Amounts & fees
|
|
297
|
-
/**
|
|
298
|
-
* @inheritDoc
|
|
299
|
-
*/
|
|
300
|
-
getInputToken() {
|
|
301
|
-
return Token_1.BitcoinTokens.BTC;
|
|
302
|
-
}
|
|
303
|
-
/**
|
|
304
|
-
* @inheritDoc
|
|
305
|
-
*/
|
|
306
|
-
getInput() {
|
|
307
|
-
return (0, TokenAmount_1.toTokenAmount)(this.amount ?? null, this.inputToken, this.wrapper._prices);
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Returns claimer bounty, acting as a reward for watchtowers to claim the swap automatically,
|
|
311
|
-
* this amount is pre-funded by the user on the destination chain when the swap escrow
|
|
312
|
-
* is initiated. For total pre-funded deposit amount see {@link getTotalDeposit}.
|
|
313
|
-
*/
|
|
314
|
-
getClaimerBounty() {
|
|
315
|
-
return (0, TokenAmount_1.toTokenAmount)(this._data.getClaimerBounty(), this.wrapper._tokens[this._data.getDepositToken()], this.wrapper._prices);
|
|
316
|
-
}
|
|
317
|
-
//////////////////////////////
|
|
318
|
-
//// Bitcoin tx
|
|
319
|
-
/**
|
|
320
|
-
* If the required number of confirmations is not known, this function tries to infer it by looping through
|
|
321
|
-
* possible confirmation targets and comparing the claim hashes
|
|
322
|
-
*
|
|
323
|
-
* @param btcTx Bitcoin transaction
|
|
324
|
-
* @param vout Output index of the desired output in the bitcoin transaction
|
|
325
|
-
*
|
|
326
|
-
* @private
|
|
327
|
-
*/
|
|
328
|
-
inferRequiredConfirmationsCount(btcTx, vout) {
|
|
329
|
-
const txOut = btcTx.outs[vout];
|
|
330
|
-
for (let i = 1; i <= 20; i++) {
|
|
331
|
-
const computedClaimHash = this._contract.getHashForOnchain(buffer_1.Buffer.from(txOut.scriptPubKey.hex, "hex"), BigInt(txOut.value), i);
|
|
332
|
-
if (computedClaimHash.toString("hex") === this._data.getClaimHash()) {
|
|
333
|
-
return i;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* @inheritDoc
|
|
339
|
-
*/
|
|
340
|
-
getRequiredConfirmationsCount() {
|
|
341
|
-
return this.requiredConfirmations ?? NaN;
|
|
342
|
-
}
|
|
343
|
-
/**
|
|
344
|
-
* Checks whether a bitcoin payment was already made, returns the payment or `null` when no payment has been made.
|
|
345
|
-
*
|
|
346
|
-
* @internal
|
|
347
|
-
*/
|
|
348
|
-
async getBitcoinPayment() {
|
|
349
|
-
const txoHashHint = this._data.getTxoHashHint();
|
|
350
|
-
if (txoHashHint == null)
|
|
351
|
-
throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
|
|
352
|
-
if (this.address == null)
|
|
353
|
-
throw new Error("Cannot check bitcoin payment, because the address is not known! This can happen after a swap is recovered.");
|
|
354
|
-
const result = await this.wrapper._btcRpc.checkAddressTxos(this.address, buffer_1.Buffer.from(txoHashHint, "hex"));
|
|
355
|
-
if (result == null)
|
|
356
|
-
return null;
|
|
357
|
-
if (this.requiredConfirmations == null) {
|
|
358
|
-
this.requiredConfirmations = this.inferRequiredConfirmationsCount(result.tx, result.vout);
|
|
359
|
-
}
|
|
360
|
-
return {
|
|
361
|
-
inputAddresses: result.tx.inputAddresses,
|
|
362
|
-
txId: result.tx.txid,
|
|
363
|
-
vout: result.vout,
|
|
364
|
-
confirmations: result.tx.confirmations ?? 0,
|
|
365
|
-
targetConfirmations: this.getRequiredConfirmationsCount()
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Used to set the txId of the bitcoin payment from the on-chain events listener
|
|
370
|
-
*
|
|
371
|
-
* @param txId Transaction ID that settled the swap on the smart chain
|
|
372
|
-
*
|
|
373
|
-
* @internal
|
|
374
|
-
*/
|
|
375
|
-
async _setBitcoinTxId(txId) {
|
|
376
|
-
if (this.txId !== txId || this.address == null || this.vout == null || this.senderAddress == null || this.amount == null) {
|
|
377
|
-
const btcTx = await this.wrapper._btcRpc.getTransaction(txId);
|
|
378
|
-
if (btcTx == null)
|
|
379
|
-
return;
|
|
380
|
-
const txoHashHint = this._data.getTxoHashHint();
|
|
381
|
-
if (txoHashHint != null) {
|
|
382
|
-
const expectedTxoHash = buffer_1.Buffer.from(txoHashHint, "hex");
|
|
383
|
-
const vout = btcTx.outs.findIndex(out => (0, Utils_1.getTxoHash)(out.scriptPubKey.hex, out.value).equals(expectedTxoHash));
|
|
384
|
-
if (vout !== -1) {
|
|
385
|
-
this.vout = vout;
|
|
386
|
-
//If amount or address are not known, parse them from the bitcoin tx
|
|
387
|
-
// this can happen if the swap is recovered from on-chain data and
|
|
388
|
-
// hence doesn't contain the address and amount data
|
|
389
|
-
if (this.amount == null)
|
|
390
|
-
this.amount = BigInt(btcTx.outs[vout].value);
|
|
391
|
-
if (this.address == null)
|
|
392
|
-
try {
|
|
393
|
-
this.address = (0, BitcoinUtils_1.fromOutputScript)(this.wrapper._options.bitcoinNetwork, btcTx.outs[vout].scriptPubKey.hex);
|
|
394
|
-
}
|
|
395
|
-
catch (e) {
|
|
396
|
-
this.logger.warn("_setBitcoinTxId(): Failed to parse address from output script: ", e);
|
|
397
|
-
}
|
|
398
|
-
if (this.requiredConfirmations == null) {
|
|
399
|
-
this.requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
if (btcTx.inputAddresses != null) {
|
|
404
|
-
this.senderAddress = btcTx.inputAddresses[0];
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
this.txId = txId;
|
|
408
|
-
}
|
|
409
|
-
/**
|
|
410
|
-
* @inheritDoc
|
|
411
|
-
*
|
|
412
|
-
* @throws {Error} if in invalid state (must be {@link FromBTCSwapState.CLAIM_COMMITED})
|
|
413
|
-
*/
|
|
414
|
-
async waitForBitcoinTransaction(updateCallback, checkIntervalSeconds, abortSignal) {
|
|
415
|
-
if (this._state !== FromBTCSwapState.CLAIM_COMMITED && this._state !== FromBTCSwapState.EXPIRED)
|
|
416
|
-
throw new Error("Must be in COMMITED state!");
|
|
417
|
-
const txoHashHint = this._data.getTxoHashHint();
|
|
418
|
-
if (txoHashHint == null)
|
|
419
|
-
throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
|
|
420
|
-
if (this.address == null)
|
|
421
|
-
throw new Error("Cannot check bitcoin payment, because the address is not known! This can happen after a swap is recovered.");
|
|
422
|
-
let abortedDueToEnoughConfirmationsResult;
|
|
423
|
-
const abortController = (0, Utils_1.extendAbortController)(abortSignal);
|
|
424
|
-
const result = await this.wrapper._btcRpc.waitForAddressTxo(this.address, buffer_1.Buffer.from(txoHashHint, "hex"), this.requiredConfirmations ?? 6, //In case confirmation count is not known, we use a conservative estimate
|
|
425
|
-
(btcTx, vout, txEtaMs) => {
|
|
426
|
-
let requiredConfirmations = this.requiredConfirmations;
|
|
427
|
-
if (btcTx != null && vout != null && requiredConfirmations == null) {
|
|
428
|
-
requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
|
|
429
|
-
}
|
|
430
|
-
if (btcTx != null && (btcTx.txid !== this.txId ||
|
|
431
|
-
this.vout == null ||
|
|
432
|
-
this.senderAddress == null ||
|
|
433
|
-
(this.requiredConfirmations == null && requiredConfirmations != null))) {
|
|
434
|
-
this.txId = btcTx.txid;
|
|
435
|
-
this.vout = vout;
|
|
436
|
-
this.requiredConfirmations = requiredConfirmations;
|
|
437
|
-
if (btcTx.inputAddresses != null)
|
|
438
|
-
this.senderAddress = btcTx.inputAddresses[0];
|
|
439
|
-
this._saveAndEmit().catch(e => {
|
|
440
|
-
this.logger.error("waitForBitcoinTransaction(): Failed to save swap from within waitForAddressTxo callback:", e);
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
//Abort the loop as soon as the transaction gets enough confirmations, this is required in case
|
|
444
|
-
// we pass a default 6 confirmations to the fn, but then are able to infer the actual confirmation
|
|
445
|
-
// target from the prior block
|
|
446
|
-
if (btcTx?.confirmations != null && requiredConfirmations != null && requiredConfirmations <= btcTx.confirmations && vout != null) {
|
|
447
|
-
abortedDueToEnoughConfirmationsResult = {
|
|
448
|
-
tx: btcTx,
|
|
449
|
-
vout
|
|
450
|
-
};
|
|
451
|
-
abortController.abort();
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
if (updateCallback != null)
|
|
455
|
-
updateCallback(btcTx?.txid, btcTx == null ? undefined : (btcTx?.confirmations ?? 0), requiredConfirmations ?? NaN, txEtaMs);
|
|
456
|
-
}, abortController.signal, checkIntervalSeconds).catch(e => {
|
|
457
|
-
//We catch the case when the loop was aborted due to the transaction getting enough confirmations
|
|
458
|
-
if (abortedDueToEnoughConfirmationsResult != null)
|
|
459
|
-
return abortedDueToEnoughConfirmationsResult;
|
|
460
|
-
throw e;
|
|
461
|
-
});
|
|
462
|
-
if (abortSignal != null)
|
|
463
|
-
abortSignal.throwIfAborted();
|
|
464
|
-
this.txId = result.tx.txid;
|
|
465
|
-
this.vout = result.vout;
|
|
466
|
-
if (result.tx.inputAddresses != null)
|
|
467
|
-
this.senderAddress = result.tx.inputAddresses[0];
|
|
468
|
-
if (this._state !== FromBTCSwapState.CLAIM_CLAIMED &&
|
|
469
|
-
this._state !== FromBTCSwapState.FAILED) {
|
|
470
|
-
this.btcTxConfirmedAt ??= Date.now();
|
|
471
|
-
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
472
|
-
}
|
|
473
|
-
await this._saveAndEmit();
|
|
474
|
-
return result.tx.txid;
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Private getter of the funded PSBT that doesn't check current state
|
|
478
|
-
*
|
|
479
|
-
* @param _bitcoinWallet Bitcoin wallet to fund the PSBT with
|
|
480
|
-
* @param feeRate Optional bitcoin fee rate in sats/vB
|
|
481
|
-
* @param additionalOutputs Optional additional outputs that should also be included in the generated PSBT
|
|
482
|
-
*
|
|
483
|
-
* @private
|
|
484
|
-
*/
|
|
485
|
-
async _getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs) {
|
|
486
|
-
if (this.address == null)
|
|
487
|
-
throw new Error("Cannot create funded PSBT, because the address is not known! This can happen after a swap is recovered.");
|
|
488
|
-
let bitcoinWallet;
|
|
489
|
-
if ((0, IBitcoinWallet_1.isIBitcoinWallet)(_bitcoinWallet)) {
|
|
490
|
-
bitcoinWallet = _bitcoinWallet;
|
|
491
|
-
}
|
|
492
|
-
else {
|
|
493
|
-
bitcoinWallet = new SingleAddressBitcoinWallet_1.SingleAddressBitcoinWallet(this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork, _bitcoinWallet);
|
|
494
|
-
}
|
|
495
|
-
//TODO: Maybe re-introduce fee rate check here if passed from the user
|
|
496
|
-
if (feeRate == null) {
|
|
497
|
-
feeRate = await bitcoinWallet.getFeeRate();
|
|
498
|
-
}
|
|
499
|
-
const basePsbt = new btc_signer_1.Transaction({
|
|
500
|
-
allowUnknownOutputs: true,
|
|
501
|
-
allowLegacyWitnessUtxo: true
|
|
502
|
-
});
|
|
503
|
-
basePsbt.addOutput({
|
|
504
|
-
amount: this.amount,
|
|
505
|
-
script: (0, BitcoinUtils_1.toOutputScript)(this.wrapper._options.bitcoinNetwork, this.address)
|
|
506
|
-
});
|
|
507
|
-
if (additionalOutputs != null)
|
|
508
|
-
additionalOutputs.forEach(output => {
|
|
509
|
-
basePsbt.addOutput({
|
|
510
|
-
amount: output.amount,
|
|
511
|
-
script: output.outputScript ?? (0, BitcoinUtils_1.toOutputScript)(this.wrapper._options.bitcoinNetwork, output.address)
|
|
512
|
-
});
|
|
513
|
-
});
|
|
514
|
-
const psbt = await bitcoinWallet.fundPsbt(basePsbt, feeRate);
|
|
515
|
-
//Sign every input
|
|
516
|
-
const signInputs = [];
|
|
517
|
-
for (let i = 0; i < psbt.inputsLength; i++) {
|
|
518
|
-
signInputs.push(i);
|
|
519
|
-
}
|
|
520
|
-
const serializedPsbt = buffer_1.Buffer.from(psbt.toPSBT());
|
|
521
|
-
return {
|
|
522
|
-
psbt,
|
|
523
|
-
psbtHex: serializedPsbt.toString("hex"),
|
|
524
|
-
psbtBase64: serializedPsbt.toString("base64"),
|
|
525
|
-
signInputs,
|
|
526
|
-
feeRate
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
/**
|
|
530
|
-
* @inheritDoc
|
|
531
|
-
*/
|
|
532
|
-
getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs) {
|
|
533
|
-
if (this._state !== FromBTCSwapState.CLAIM_COMMITED)
|
|
534
|
-
throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
|
|
535
|
-
if (this.txId != null)
|
|
536
|
-
throw new Error("Bitcoin transaction already submitted for this swap!");
|
|
537
|
-
return this._getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs);
|
|
538
|
-
}
|
|
539
|
-
/**
|
|
540
|
-
* @inheritDoc
|
|
541
|
-
*
|
|
542
|
-
* @throws {Error} if the swap is in invalid state (not in {@link FromBTCSwapState.CLAIM_COMMITED}), or if
|
|
543
|
-
* the swap bitcoin address already expired.
|
|
544
|
-
*/
|
|
545
|
-
async submitPsbt(_psbt) {
|
|
546
|
-
const psbt = (0, BitcoinUtils_1.parsePsbtTransaction)(_psbt);
|
|
547
|
-
if (this._state !== FromBTCSwapState.CLAIM_COMMITED)
|
|
548
|
-
throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
|
|
549
|
-
if (this.txId != null)
|
|
550
|
-
throw new Error("Bitcoin transaction already submitted for this swap!");
|
|
551
|
-
//Ensure not expired
|
|
552
|
-
if (this.getTimeoutTime() < Date.now()) {
|
|
553
|
-
throw new Error("Swap address expired!");
|
|
554
|
-
}
|
|
555
|
-
const output0 = psbt.getOutput(0);
|
|
556
|
-
if (this.amount != null && output0.amount !== this.amount)
|
|
557
|
-
throw new Error("PSBT output amount invalid, expected: " + this.amount + " got: " + output0.amount);
|
|
558
|
-
if (this.address != null) {
|
|
559
|
-
const expectedOutputScript = (0, BitcoinUtils_1.toOutputScript)(this.wrapper._options.bitcoinNetwork, this.address);
|
|
560
|
-
if (output0.script == null || !expectedOutputScript.equals(output0.script))
|
|
561
|
-
throw new Error("PSBT output script invalid!");
|
|
562
|
-
}
|
|
563
|
-
if (!psbt.isFinal)
|
|
564
|
-
psbt.finalize();
|
|
565
|
-
const txId = await this.wrapper._btcRpc.sendRawTransaction(buffer_1.Buffer.from(psbt.toBytes(true, true)).toString("hex"));
|
|
566
|
-
await this._setSubmittedBitcoinTx(txId, psbt);
|
|
567
|
-
return txId;
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* @inheritDoc
|
|
571
|
-
*/
|
|
572
|
-
async estimateBitcoinFee(_bitcoinWallet, feeRate) {
|
|
573
|
-
if (this.address == null || this.amount == null)
|
|
574
|
-
return null;
|
|
575
|
-
const bitcoinWallet = (0, BitcoinWalletUtils_1.toBitcoinWallet)(_bitcoinWallet, this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork);
|
|
576
|
-
const txFee = await bitcoinWallet.getTransactionFee(this.address, this.amount, feeRate);
|
|
577
|
-
if (txFee == null)
|
|
578
|
-
return null;
|
|
579
|
-
return (0, TokenAmount_1.toTokenAmount)(BigInt(txFee), Token_1.BitcoinTokens.BTC, this.wrapper._prices);
|
|
580
|
-
}
|
|
581
|
-
/**
|
|
582
|
-
* @inheritDoc
|
|
583
|
-
*/
|
|
584
|
-
async sendBitcoinTransaction(wallet, feeRate) {
|
|
585
|
-
if (this.address == null || this.amount == null)
|
|
586
|
-
throw new Error("Cannot send bitcoin transaction, because the address is not known! This can happen after a swap is recovered.");
|
|
587
|
-
if (this._state !== FromBTCSwapState.CLAIM_COMMITED)
|
|
588
|
-
throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
|
|
589
|
-
if (this.txId != null)
|
|
590
|
-
throw new Error("Bitcoin transaction already submitted for this swap!");
|
|
591
|
-
//Ensure not expired
|
|
592
|
-
if (this.getTimeoutTime() < Date.now()) {
|
|
593
|
-
throw new Error("Swap address expired!");
|
|
594
|
-
}
|
|
595
|
-
if ((0, IBitcoinWallet_1.isIBitcoinWallet)(wallet)) {
|
|
596
|
-
const txId = await wallet.sendTransaction(this.address, this.amount, feeRate);
|
|
597
|
-
await this._setSubmittedBitcoinTx(txId);
|
|
598
|
-
return txId;
|
|
599
|
-
}
|
|
600
|
-
else {
|
|
601
|
-
const { psbt, psbtHex, psbtBase64, signInputs } = await this.getFundedPsbt(wallet, feeRate);
|
|
602
|
-
const signedPsbt = await wallet.signPsbt({
|
|
603
|
-
psbt, psbtHex, psbtBase64
|
|
604
|
-
}, signInputs);
|
|
605
|
-
return await this.submitPsbt(signedPsbt);
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
//////////////////////////////
|
|
609
|
-
//// Execution
|
|
610
|
-
/**
|
|
611
|
-
* Executes the swap with the provided bitcoin wallet,
|
|
612
|
-
*
|
|
613
|
-
* @param dstSigner Signer on the destination network, needs to have the same address as the one specified when
|
|
614
|
-
* quote was created, this is required for legacy swaps because the destination wallet needs to actively open
|
|
615
|
-
* a bitcoin swap address to which the BTC is then sent, this means that the address also needs to have enough
|
|
616
|
-
* native tokens to pay for gas on the destination network
|
|
617
|
-
* @param wallet Bitcoin wallet to use to sign the bitcoin transaction, can also be null - then the execution waits
|
|
618
|
-
* till a transaction is received from an external wallet
|
|
619
|
-
* @param callbacks Callbacks to track the progress of the swap
|
|
620
|
-
* @param options Optional options for the swap like feeRate, AbortSignal, and timeouts/intervals
|
|
621
|
-
*
|
|
622
|
-
* @returns {boolean} Whether a swap was settled automatically by swap watchtowers or requires manual claim by the
|
|
623
|
-
* user, in case `false` is returned the user should call `swap.claim()` to settle the swap on the destination manually
|
|
624
|
-
*/
|
|
625
|
-
async execute(dstSigner, wallet, callbacks, options) {
|
|
626
|
-
if (this._state === FromBTCSwapState.FAILED)
|
|
627
|
-
throw new Error("Swap failed!");
|
|
628
|
-
if (this._state === FromBTCSwapState.EXPIRED)
|
|
629
|
-
throw new Error("Swap address expired!");
|
|
630
|
-
if (this._state === FromBTCSwapState.QUOTE_EXPIRED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED)
|
|
631
|
-
throw new Error("Swap quote expired!");
|
|
632
|
-
if (this._state === FromBTCSwapState.CLAIM_CLAIMED)
|
|
633
|
-
throw new Error("Swap already settled!");
|
|
634
|
-
if (this._state === FromBTCSwapState.PR_CREATED) {
|
|
635
|
-
await this.commit(dstSigner, options?.abortSignal, undefined, callbacks?.onDestinationCommitSent);
|
|
636
|
-
}
|
|
637
|
-
if (this._state === FromBTCSwapState.CLAIM_COMMITED) {
|
|
638
|
-
if (wallet != null) {
|
|
639
|
-
const bitcoinPaymentSent = await this.getBitcoinPayment();
|
|
640
|
-
if (bitcoinPaymentSent == null && this.txId == null) {
|
|
641
|
-
//Send btc tx
|
|
642
|
-
const txId = await this.sendBitcoinTransaction(wallet, options?.feeRate);
|
|
643
|
-
if (callbacks?.onSourceTransactionSent != null)
|
|
644
|
-
callbacks.onSourceTransactionSent(txId);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
const txId = await this.waitForBitcoinTransaction(callbacks?.onSourceTransactionConfirmationStatus, options?.btcTxCheckIntervalSeconds, options?.abortSignal);
|
|
648
|
-
if (callbacks?.onSourceTransactionConfirmed != null)
|
|
649
|
-
callbacks.onSourceTransactionConfirmed(txId);
|
|
650
|
-
}
|
|
651
|
-
// @ts-ignore
|
|
652
|
-
if (this._state === FromBTCSwapState.CLAIM_CLAIMED)
|
|
653
|
-
return true;
|
|
654
|
-
if (this._state === FromBTCSwapState.BTC_TX_CONFIRMED) {
|
|
655
|
-
const success = await this.waitTillClaimed(options?.maxWaitTillAutomaticSettlementSeconds ?? 60, options?.abortSignal);
|
|
656
|
-
if (success && callbacks?.onSwapSettled != null)
|
|
657
|
-
callbacks.onSwapSettled(this.getOutputTxId());
|
|
658
|
-
return success;
|
|
659
|
-
}
|
|
660
|
-
throw new Error("Invalid state reached!");
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* @internal
|
|
664
|
-
*/
|
|
665
|
-
async _getExecutionStatus(options) {
|
|
666
|
-
const state = this._state;
|
|
667
|
-
const now = Date.now();
|
|
668
|
-
const timeoutTime = this.getTimeoutTime();
|
|
669
|
-
let confirmations;
|
|
670
|
-
let bitcoinTxId;
|
|
671
|
-
let destinationSetupStatus = "awaiting";
|
|
672
|
-
let bitcoinPaymentStatus = "inactive";
|
|
673
|
-
let destinationSettlementStatus = "inactive";
|
|
674
|
-
let buildCurrentAction = async () => undefined;
|
|
675
|
-
switch (state) {
|
|
676
|
-
case FromBTCSwapState.PR_CREATED: {
|
|
677
|
-
const quoteValid = await this._verifyQuoteValid();
|
|
678
|
-
destinationSetupStatus = quoteValid && timeoutTime >= now ? "awaiting" : "soft_expired";
|
|
679
|
-
if (quoteValid && timeoutTime >= now) {
|
|
680
|
-
buildCurrentAction = this._buildInitSmartChainTxAction.bind(this);
|
|
681
|
-
}
|
|
682
|
-
break;
|
|
683
|
-
}
|
|
684
|
-
case FromBTCSwapState.QUOTE_SOFT_EXPIRED:
|
|
685
|
-
destinationSetupStatus = "soft_expired";
|
|
686
|
-
break;
|
|
687
|
-
case FromBTCSwapState.QUOTE_EXPIRED:
|
|
688
|
-
destinationSetupStatus = "expired";
|
|
689
|
-
break;
|
|
690
|
-
case FromBTCSwapState.CLAIM_COMMITED:
|
|
691
|
-
case FromBTCSwapState.EXPIRED:
|
|
692
|
-
case FromBTCSwapState.FAILED:
|
|
693
|
-
const bitcoinPayment = this.address == null ? null : await this.getBitcoinPayment();
|
|
694
|
-
bitcoinTxId = bitcoinPayment?.txId;
|
|
695
|
-
let bitcoinConfirmationDelay;
|
|
696
|
-
if (bitcoinPayment != null && bitcoinPayment.confirmations < bitcoinPayment.targetConfirmations) {
|
|
697
|
-
const tx = await this.wrapper._btcRpc.getTransaction(bitcoinPayment.txId);
|
|
698
|
-
const result = tx == null
|
|
699
|
-
? null
|
|
700
|
-
: await this.wrapper._btcRpc.getConfirmationDelay(tx, bitcoinPayment.targetConfirmations);
|
|
701
|
-
bitcoinConfirmationDelay = result ?? -1;
|
|
702
|
-
}
|
|
703
|
-
destinationSetupStatus = "completed";
|
|
704
|
-
if (bitcoinPayment == null) {
|
|
705
|
-
if (this.txId != null) {
|
|
706
|
-
bitcoinPaymentStatus = state === FromBTCSwapState.FAILED ? "expired" : "received";
|
|
707
|
-
if (state !== FromBTCSwapState.FAILED) {
|
|
708
|
-
buildCurrentAction = this._buildWaitBitcoinConfirmationsAction.bind(this, -1, "Wait for bitcoin transaction to be picked up by the RPC and confirmed.");
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
else {
|
|
712
|
-
bitcoinPaymentStatus = "awaiting";
|
|
713
|
-
if (state === FromBTCSwapState.EXPIRED)
|
|
714
|
-
bitcoinPaymentStatus = "soft_expired";
|
|
715
|
-
if (state === FromBTCSwapState.FAILED)
|
|
716
|
-
bitcoinPaymentStatus = "expired";
|
|
717
|
-
if (state === FromBTCSwapState.CLAIM_COMMITED && timeoutTime >= now &&
|
|
718
|
-
this.address != null && this.amount != null) {
|
|
719
|
-
buildCurrentAction = this._buildSendToAddressOrSignPsbtAction.bind(this);
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
else if (bitcoinPayment.confirmations >= bitcoinPayment.targetConfirmations) {
|
|
724
|
-
bitcoinPaymentStatus = "confirmed";
|
|
725
|
-
if (state !== FromBTCSwapState.FAILED) {
|
|
726
|
-
buildCurrentAction = this._buildWaitBitcoinConfirmationsAction.bind(this, bitcoinConfirmationDelay ?? -1, undefined);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
else {
|
|
730
|
-
bitcoinPaymentStatus = "received";
|
|
731
|
-
confirmations = {
|
|
732
|
-
current: bitcoinPayment.confirmations,
|
|
733
|
-
target: bitcoinPayment.targetConfirmations,
|
|
734
|
-
etaSeconds: bitcoinConfirmationDelay ?? -1
|
|
735
|
-
};
|
|
736
|
-
if (state !== FromBTCSwapState.FAILED) {
|
|
737
|
-
buildCurrentAction = this._buildWaitBitcoinConfirmationsAction.bind(this, bitcoinConfirmationDelay ?? -1, undefined);
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
destinationSettlementStatus = state === FromBTCSwapState.FAILED ? "expired" : "inactive";
|
|
741
|
-
break;
|
|
742
|
-
case FromBTCSwapState.BTC_TX_CONFIRMED:
|
|
743
|
-
destinationSetupStatus = "completed";
|
|
744
|
-
bitcoinPaymentStatus = "confirmed";
|
|
745
|
-
if (this.btcTxConfirmedAt == null ||
|
|
746
|
-
options?.maxWaitTillAutomaticSettlementSeconds === 0 ||
|
|
747
|
-
(now - this.btcTxConfirmedAt) > (options?.maxWaitTillAutomaticSettlementSeconds ?? 60) * 1000) {
|
|
748
|
-
destinationSettlementStatus = "awaiting_manual";
|
|
749
|
-
buildCurrentAction = this._buildClaimSmartChainTxAction.bind(this);
|
|
750
|
-
}
|
|
751
|
-
else {
|
|
752
|
-
destinationSettlementStatus = "awaiting_automatic";
|
|
753
|
-
buildCurrentAction = this._buildWaitSettlementAction.bind(this, options?.maxWaitTillAutomaticSettlementSeconds);
|
|
754
|
-
}
|
|
755
|
-
break;
|
|
756
|
-
case FromBTCSwapState.CLAIM_CLAIMED:
|
|
757
|
-
destinationSetupStatus = "completed";
|
|
758
|
-
bitcoinPaymentStatus = "confirmed";
|
|
759
|
-
destinationSettlementStatus = "settled";
|
|
760
|
-
break;
|
|
761
|
-
}
|
|
762
|
-
if (bitcoinPaymentStatus === "confirmed") {
|
|
763
|
-
const requiredConfirmations = this.getRequiredConfirmationsCount();
|
|
764
|
-
if (!Number.isNaN(requiredConfirmations)) {
|
|
765
|
-
confirmations = {
|
|
766
|
-
current: requiredConfirmations,
|
|
767
|
-
target: requiredConfirmations,
|
|
768
|
-
etaSeconds: 0
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
return {
|
|
773
|
-
steps: [
|
|
774
|
-
{
|
|
775
|
-
type: "Setup",
|
|
776
|
-
side: "destination",
|
|
777
|
-
chain: this.chainIdentifier,
|
|
778
|
-
title: "Open Bitcoin swap address",
|
|
779
|
-
description: `Create the escrow on the ${this.chainIdentifier} side to open the Bitcoin swap address`,
|
|
780
|
-
status: destinationSetupStatus,
|
|
781
|
-
setupTxId: this._commitTxId
|
|
782
|
-
},
|
|
783
|
-
{
|
|
784
|
-
type: "Payment",
|
|
785
|
-
side: "source",
|
|
786
|
-
chain: "BITCOIN",
|
|
787
|
-
title: "Bitcoin payment",
|
|
788
|
-
description: "Send Bitcoin to the swap address and wait for the transaction to confirm",
|
|
789
|
-
status: bitcoinPaymentStatus,
|
|
790
|
-
confirmations,
|
|
791
|
-
initTxId: this.txId ?? bitcoinTxId,
|
|
792
|
-
settleTxId: this.txId
|
|
793
|
-
},
|
|
794
|
-
{
|
|
795
|
-
type: "Settlement",
|
|
796
|
-
side: "destination",
|
|
797
|
-
chain: this.chainIdentifier,
|
|
798
|
-
title: "Destination settlement",
|
|
799
|
-
description: `Wait for automatic settlement on the ${this.chainIdentifier} side, or settle manually if it takes too long`,
|
|
800
|
-
status: destinationSettlementStatus,
|
|
801
|
-
initTxId: this._commitTxId,
|
|
802
|
-
settleTxId: this._claimTxId
|
|
803
|
-
}
|
|
804
|
-
],
|
|
805
|
-
buildCurrentAction,
|
|
806
|
-
state
|
|
807
|
-
};
|
|
808
|
-
}
|
|
809
|
-
/**
|
|
810
|
-
* @inheritDoc
|
|
811
|
-
* @internal
|
|
812
|
-
*/
|
|
813
|
-
async _submitExecutionTransactions(txs, abortSignal, requiredStates, idempotent) {
|
|
814
|
-
if (txs.length === 0)
|
|
815
|
-
throw new Error("Need to submit at least 1 transaction in the array, submitted empty array of transactions!");
|
|
816
|
-
if (idempotent) {
|
|
817
|
-
// Handle idempotent calls
|
|
818
|
-
let idempotencyTriggered = false;
|
|
819
|
-
const txIds = [];
|
|
820
|
-
for (let tx of txs) {
|
|
821
|
-
let parsedTx;
|
|
822
|
-
if (typeof (tx) === "string") {
|
|
823
|
-
try {
|
|
824
|
-
parsedTx = await this.wrapper._chain.deserializeSignedTx(tx);
|
|
825
|
-
}
|
|
826
|
-
catch (e) { }
|
|
827
|
-
try {
|
|
828
|
-
parsedTx = (0, BitcoinUtils_1.parsePsbtTransaction)(tx);
|
|
829
|
-
}
|
|
830
|
-
catch (e) { }
|
|
831
|
-
}
|
|
832
|
-
else {
|
|
833
|
-
parsedTx = tx;
|
|
834
|
-
}
|
|
835
|
-
if (parsedTx == null) {
|
|
836
|
-
this.logger.debug("_submitExecutionTransactions(): Failed to parse provided execution transaction: ", tx);
|
|
837
|
-
continue;
|
|
838
|
-
}
|
|
839
|
-
if (parsedTx instanceof btc_signer_1.Transaction) {
|
|
840
|
-
// Bitcoin tx
|
|
841
|
-
const btcTx = await this.wrapper._btcRpc.parseTransaction(buffer_1.Buffer.from(parsedTx.toBytes(true)).toString("hex"));
|
|
842
|
-
if (btcTx.txid === this.txId)
|
|
843
|
-
idempotencyTriggered = true;
|
|
844
|
-
txIds.push(btcTx.txid);
|
|
845
|
-
}
|
|
846
|
-
else {
|
|
847
|
-
// SC tx
|
|
848
|
-
if (this.wrapper._chain.getTxId != null) {
|
|
849
|
-
const txId = await this.wrapper._chain.getTxId(parsedTx);
|
|
850
|
-
if (this._commitTxId === txId || this._claimTxId === txId)
|
|
851
|
-
idempotencyTriggered = true;
|
|
852
|
-
txIds.push(txId);
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
if (idempotencyTriggered)
|
|
857
|
-
return txIds;
|
|
858
|
-
}
|
|
859
|
-
if (requiredStates != null && !requiredStates.includes(this._state))
|
|
860
|
-
throw new Error("Swap state has changed before transactions were submitted!");
|
|
861
|
-
if (this._state === FromBTCSwapState.CLAIM_COMMITED) {
|
|
862
|
-
let psbt;
|
|
863
|
-
if (txs.length !== 1)
|
|
864
|
-
throw new Error("Need to submit exactly 1 signed PSBT!");
|
|
865
|
-
if (typeof (txs[0]) !== "string" && !(txs[0] instanceof btc_signer_1.Transaction))
|
|
866
|
-
throw new Error("Must submit a valid PSBT as hex/base64 string or `@scure/btc-signer` Transaction object!");
|
|
867
|
-
psbt = txs[0];
|
|
868
|
-
return [await this.submitPsbt(psbt)];
|
|
869
|
-
}
|
|
870
|
-
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
871
|
-
if (!await this._verifyQuoteValid())
|
|
872
|
-
throw new Error("Quote is already expired!");
|
|
873
|
-
if (this.getTimeoutTime() < Date.now())
|
|
874
|
-
throw new Error("Swap address already expired or close to expiry!");
|
|
875
|
-
const parsedTxs = [];
|
|
876
|
-
for (let tx of txs) {
|
|
877
|
-
parsedTxs.push(typeof (tx) === "string" ? await this.wrapper._chain.deserializeSignedTx(tx) : tx);
|
|
878
|
-
}
|
|
879
|
-
const txIds = await this.wrapper._chain.sendSignedAndConfirm(parsedTxs, true, abortSignal, false);
|
|
880
|
-
await this.waitTillCommited(abortSignal);
|
|
881
|
-
return txIds;
|
|
882
|
-
}
|
|
883
|
-
if (this._state === FromBTCSwapState.BTC_TX_CONFIRMED) {
|
|
884
|
-
const parsedTxs = [];
|
|
885
|
-
for (let tx of txs) {
|
|
886
|
-
parsedTxs.push(typeof (tx) === "string" ? await this.wrapper._chain.deserializeSignedTx(tx) : tx);
|
|
887
|
-
}
|
|
888
|
-
const txIds = await this.wrapper._chain.sendSignedAndConfirm(parsedTxs, true, abortSignal, false);
|
|
889
|
-
await this.waitTillClaimed(undefined, abortSignal);
|
|
890
|
-
return txIds;
|
|
891
|
-
}
|
|
892
|
-
throw new Error("Invalid swap state for transaction submission!");
|
|
893
|
-
}
|
|
894
|
-
/**
|
|
895
|
-
* @internal
|
|
896
|
-
*/
|
|
897
|
-
async _buildSendToAddressOrSignPsbtAction(actionOptions) {
|
|
898
|
-
if (this.address == null)
|
|
899
|
-
throw new Error("Bitcoin swap address not known!");
|
|
900
|
-
if (this.amount == null)
|
|
901
|
-
throw new Error("Bitcoin swap amount not known!");
|
|
902
|
-
if (actionOptions?.bitcoinWallet == null) {
|
|
903
|
-
return {
|
|
904
|
-
type: "SendToAddress",
|
|
905
|
-
name: "Deposit on Bitcoin",
|
|
906
|
-
description: "Send funds to the bitcoin swap address",
|
|
907
|
-
chain: "BITCOIN",
|
|
908
|
-
txs: [{
|
|
909
|
-
type: "BITCOIN_ADDRESS",
|
|
910
|
-
address: this.address,
|
|
911
|
-
hyperlink: this._getHyperlink(),
|
|
912
|
-
amount: (0, TokenAmount_1.toTokenAmount)(this.amount, Token_1.BitcoinTokens.BTC, this.wrapper._prices)
|
|
913
|
-
}],
|
|
914
|
-
waitForTransactions: async (maxWaitTimeSeconds, pollIntervalSeconds, abortSignal) => {
|
|
915
|
-
let btcTxId;
|
|
916
|
-
const abortController = (0, Utils_1.extendAbortController)(abortSignal, maxWaitTimeSeconds, "Timed out waiting for bitcoin transaction");
|
|
917
|
-
try {
|
|
918
|
-
return await this.waitForBitcoinTransaction((txId) => {
|
|
919
|
-
btcTxId = txId;
|
|
920
|
-
abortController.abort();
|
|
921
|
-
}, pollIntervalSeconds, abortController.signal);
|
|
922
|
-
}
|
|
923
|
-
catch (e) {
|
|
924
|
-
if (btcTxId != null)
|
|
925
|
-
return btcTxId;
|
|
926
|
-
throw e;
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
};
|
|
930
|
-
}
|
|
931
|
-
return {
|
|
932
|
-
type: "SignPSBT",
|
|
933
|
-
name: "Deposit on Bitcoin",
|
|
934
|
-
description: "Send funds to the bitcoin swap address",
|
|
935
|
-
chain: "BITCOIN",
|
|
936
|
-
txs: [{
|
|
937
|
-
...await this.getFundedPsbt(actionOptions.bitcoinWallet, actionOptions?.bitcoinFeeRate),
|
|
938
|
-
type: "FUNDED_PSBT"
|
|
939
|
-
}],
|
|
940
|
-
submitPsbt: async (signedPsbt, idempotent) => {
|
|
941
|
-
return this._submitExecutionTransactions(Array.isArray(signedPsbt) ? signedPsbt : [signedPsbt], undefined, [FromBTCSwapState.CLAIM_COMMITED], idempotent);
|
|
942
|
-
}
|
|
943
|
-
};
|
|
944
|
-
}
|
|
945
|
-
/**
|
|
946
|
-
* @internal
|
|
947
|
-
*/
|
|
948
|
-
async _buildWaitBitcoinConfirmationsAction(confirmationDelay, description) {
|
|
949
|
-
return {
|
|
950
|
-
type: "Wait",
|
|
951
|
-
name: "Bitcoin confirmations",
|
|
952
|
-
description: description ?? "Wait for bitcoin transaction to confirm",
|
|
953
|
-
pollTimeSeconds: 10,
|
|
954
|
-
expectedTimeSeconds: confirmationDelay === -1 ? -1 : Math.floor(confirmationDelay / 1000),
|
|
955
|
-
wait: async (maxWaitTimeSeconds, pollIntervalSeconds, abortSignal, btcConfirmationsCallback) => {
|
|
956
|
-
const abortController = (0, Utils_1.extendAbortController)(abortSignal, maxWaitTimeSeconds, "Timed out waiting for bitcoin transaction to confirm");
|
|
957
|
-
await this.waitForBitcoinTransaction(btcConfirmationsCallback, pollIntervalSeconds, abortController.signal);
|
|
958
|
-
}
|
|
959
|
-
};
|
|
960
|
-
}
|
|
961
|
-
/**
|
|
962
|
-
* @internal
|
|
963
|
-
*/
|
|
964
|
-
async _buildWaitSettlementAction(maxWaitTillAutomaticSettlementSeconds) {
|
|
965
|
-
return {
|
|
966
|
-
type: "Wait",
|
|
967
|
-
name: "Automatic settlement",
|
|
968
|
-
description: "Wait for automatic settlement by the watchtower",
|
|
969
|
-
pollTimeSeconds: 5,
|
|
970
|
-
expectedTimeSeconds: 10,
|
|
971
|
-
wait: async (maxWaitTimeSeconds, pollIntervalSeconds, abortSignal) => {
|
|
972
|
-
await this.waitTillClaimed(maxWaitTimeSeconds ?? maxWaitTillAutomaticSettlementSeconds ?? 60, abortSignal, pollIntervalSeconds);
|
|
973
|
-
}
|
|
974
|
-
};
|
|
975
|
-
}
|
|
976
|
-
/**
|
|
977
|
-
* @internal
|
|
978
|
-
*/
|
|
979
|
-
async _buildInitSmartChainTxAction(actionOptions) {
|
|
980
|
-
return {
|
|
981
|
-
type: "SignSmartChainTransaction",
|
|
982
|
-
name: "Initiate swap",
|
|
983
|
-
description: `Opens up the bitcoin swap address on the ${this.chainIdentifier} side`,
|
|
984
|
-
chain: this.chainIdentifier,
|
|
985
|
-
txs: await this.prepareTransactions(this.txsCommit(actionOptions?.skipChecks)),
|
|
986
|
-
submitTransactions: async (txs, abortSignal, idempotent) => {
|
|
987
|
-
return this._submitExecutionTransactions(txs, abortSignal, [FromBTCSwapState.PR_CREATED, FromBTCSwapState.QUOTE_SOFT_EXPIRED], idempotent);
|
|
988
|
-
},
|
|
989
|
-
requiredSigner: this._getInitiator()
|
|
990
|
-
};
|
|
991
|
-
}
|
|
992
|
-
/**
|
|
993
|
-
* @inheritDoc
|
|
994
|
-
* @internal
|
|
995
|
-
*/
|
|
996
|
-
async _buildClaimSmartChainTxAction(actionOptions) {
|
|
997
|
-
const signerAddress = await this.wrapper._getSignerAddress(actionOptions?.manualSettlementSmartChainSigner);
|
|
998
|
-
return {
|
|
999
|
-
type: "SignSmartChainTransaction",
|
|
1000
|
-
name: "Settle manually",
|
|
1001
|
-
description: "Manually settle (claim) the swap on the destination smart chain",
|
|
1002
|
-
chain: this.chainIdentifier,
|
|
1003
|
-
txs: await this.prepareTransactions(this.txsClaim(actionOptions?.manualSettlementSmartChainSigner)),
|
|
1004
|
-
submitTransactions: async (txs, abortSignal, idempotent) => {
|
|
1005
|
-
return this._submitExecutionTransactions(txs, abortSignal, [FromBTCSwapState.BTC_TX_CONFIRMED], idempotent);
|
|
1006
|
-
},
|
|
1007
|
-
requiredSigner: signerAddress ?? this._getInitiator()
|
|
1008
|
-
};
|
|
1009
|
-
}
|
|
1010
|
-
/**
|
|
1011
|
-
* @inheritDoc
|
|
1012
|
-
*
|
|
1013
|
-
* @param options.bitcoinFeeRate Optional fee rate to use for the created Bitcoin transaction
|
|
1014
|
-
* @param options.bitcoinWallet Bitcoin wallet to use, when provided the function returns a funded
|
|
1015
|
-
* psbt (`"FUNDED_PSBT"`), if not passed just a bitcoin receive address is returned (`"ADDRESS"`)
|
|
1016
|
-
* @param options.skipChecks Skip checks like making sure init signature is still valid and swap
|
|
1017
|
-
* wasn't commited yet (this is handled on swap creation, if you commit right after quoting, you
|
|
1018
|
-
* can use `skipChecks=true`)
|
|
1019
|
-
* @param options.manualSettlementSmartChainSigner Optional smart chain signer to create a manual claim (settlement) transaction
|
|
1020
|
-
* @param options.maxWaitTillAutomaticSettlementSeconds Maximum time to wait for an automatic settlement after
|
|
1021
|
-
* the bitcoin transaction is confirmed (defaults to 60 seconds)
|
|
1022
|
-
*/
|
|
1023
|
-
async getExecutionAction(options) {
|
|
1024
|
-
const executionStatus = await this._getExecutionStatus(options);
|
|
1025
|
-
return executionStatus.buildCurrentAction(options);
|
|
1026
|
-
}
|
|
1027
|
-
/**
|
|
1028
|
-
* @inheritDoc
|
|
1029
|
-
*/
|
|
1030
|
-
async getExecutionStatus(options) {
|
|
1031
|
-
const executionStatus = await this._getExecutionStatus(options);
|
|
1032
|
-
return {
|
|
1033
|
-
steps: executionStatus.steps,
|
|
1034
|
-
currentAction: options?.skipBuildingAction ? undefined : await executionStatus.buildCurrentAction(options),
|
|
1035
|
-
stateInfo: this._getStateInfo(executionStatus.state)
|
|
1036
|
-
};
|
|
1037
|
-
}
|
|
1038
|
-
/**
|
|
1039
|
-
* @inheritDoc
|
|
1040
|
-
*/
|
|
1041
|
-
async getExecutionSteps(options) {
|
|
1042
|
-
return (await this._getExecutionStatus(options)).steps;
|
|
1043
|
-
}
|
|
1044
|
-
//////////////////////////////
|
|
1045
|
-
//// Commit
|
|
1046
|
-
/**
|
|
1047
|
-
* @inheritDoc
|
|
1048
|
-
*
|
|
1049
|
-
* @throws {Error} If invalid signer is provided that doesn't match the swap data
|
|
1050
|
-
*/
|
|
1051
|
-
async commit(_signer, abortSignal, skipChecks, onBeforeTxSent) {
|
|
1052
|
-
const signer = (0, base_1.isAbstractSigner)(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
|
|
1053
|
-
this.checkSigner(signer);
|
|
1054
|
-
let txCount = 0;
|
|
1055
|
-
const txs = await this.txsCommit(skipChecks);
|
|
1056
|
-
const result = await this.wrapper._chain.sendAndConfirm(signer, txs, true, abortSignal, undefined, (txId) => {
|
|
1057
|
-
txCount++;
|
|
1058
|
-
if (onBeforeTxSent != null && txCount === txs.length)
|
|
1059
|
-
onBeforeTxSent(txId);
|
|
1060
|
-
return Promise.resolve();
|
|
1061
|
-
});
|
|
1062
|
-
this._commitTxId = result[result.length - 1];
|
|
1063
|
-
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED || this._state === FromBTCSwapState.QUOTE_EXPIRED) {
|
|
1064
|
-
await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
|
|
1065
|
-
}
|
|
1066
|
-
return this._commitTxId;
|
|
1067
|
-
}
|
|
1068
|
-
/**
|
|
1069
|
-
* @inheritDoc
|
|
1070
|
-
*/
|
|
1071
|
-
async waitTillCommited(abortSignal) {
|
|
1072
|
-
if (this._state === FromBTCSwapState.CLAIM_COMMITED || this._state === FromBTCSwapState.CLAIM_CLAIMED)
|
|
1073
|
-
return Promise.resolve();
|
|
1074
|
-
if (this._state !== FromBTCSwapState.PR_CREATED && this._state !== FromBTCSwapState.QUOTE_SOFT_EXPIRED)
|
|
1075
|
-
throw new Error("Invalid state");
|
|
1076
|
-
const abortController = (0, Utils_1.extendAbortController)(abortSignal);
|
|
1077
|
-
const result = await Promise.race([
|
|
1078
|
-
this.watchdogWaitTillCommited(undefined, abortController.signal),
|
|
1079
|
-
this.waitTillState(FromBTCSwapState.CLAIM_COMMITED, "gte", abortController.signal).then(() => 0)
|
|
1080
|
-
]);
|
|
1081
|
-
abortController.abort();
|
|
1082
|
-
if (result === 0) {
|
|
1083
|
-
this.logger.debug("waitTillCommited(): Resolved from state changed");
|
|
1084
|
-
}
|
|
1085
|
-
else if (result != null) {
|
|
1086
|
-
this.logger.debug("waitTillCommited(): Resolved from watchdog - commited");
|
|
1087
|
-
}
|
|
1088
|
-
if (result === null) {
|
|
1089
|
-
this.logger.debug("waitTillCommited(): Resolved from watchdog - signature expired");
|
|
1090
|
-
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1091
|
-
await this._saveAndEmit(FromBTCSwapState.QUOTE_EXPIRED);
|
|
1092
|
-
}
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1096
|
-
if (typeof (result) === "object" && result.getInitTxId != null && this._commitTxId == null)
|
|
1097
|
-
this._commitTxId = await result.getInitTxId();
|
|
1098
|
-
await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
//////////////////////////////
|
|
1102
|
-
//// Claim
|
|
1103
|
-
/**
|
|
1104
|
-
* Returns transactions for settling (claiming) the swap if the swap requires manual settlement, you can check so
|
|
1105
|
-
* with isClaimable. After sending the transaction manually be sure to call the waitTillClaimed function to wait
|
|
1106
|
-
* till the claim transaction is observed, processed by the SDK and state of the swap properly updated.
|
|
1107
|
-
*
|
|
1108
|
-
* @remarks
|
|
1109
|
-
* Might also return transactions necessary to sync the bitcoin light client.
|
|
1110
|
-
*
|
|
1111
|
-
* @param _signer Address of the signer to create the claim transactions for
|
|
1112
|
-
*
|
|
1113
|
-
* @throws {Error} If the swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
|
|
1114
|
-
*/
|
|
1115
|
-
async txsClaim(_signer) {
|
|
1116
|
-
let signer = undefined;
|
|
1117
|
-
if (_signer != null) {
|
|
1118
|
-
if (typeof (_signer) === "string") {
|
|
1119
|
-
signer = _signer;
|
|
1120
|
-
}
|
|
1121
|
-
else if ((0, base_1.isAbstractSigner)(_signer)) {
|
|
1122
|
-
signer = _signer;
|
|
1123
|
-
}
|
|
1124
|
-
else {
|
|
1125
|
-
signer = await this.wrapper._chain.wrapSigner(_signer);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
if (this._state !== FromBTCSwapState.BTC_TX_CONFIRMED)
|
|
1129
|
-
throw new Error("Must be in BTC_TX_CONFIRMED state!");
|
|
1130
|
-
if (this.txId == null || this.vout == null)
|
|
1131
|
-
throw new Error("Bitcoin transaction ID not known!");
|
|
1132
|
-
const tx = await this.wrapper._btcRpc.getTransaction(this.txId);
|
|
1133
|
-
if (tx == null)
|
|
1134
|
-
throw new Error("Bitcoin transaction not found on the network!");
|
|
1135
|
-
this.requiredConfirmations ??= this.inferRequiredConfirmationsCount(tx, this.vout);
|
|
1136
|
-
if (this.requiredConfirmations == null)
|
|
1137
|
-
throw new Error("Cannot create claim transaction, because required confirmations are not known and cannot be infered! This can happen after a swap is recovered.");
|
|
1138
|
-
if (tx.blockhash == null || tx.confirmations == null || tx.blockheight == null || tx.confirmations < this.requiredConfirmations)
|
|
1139
|
-
throw new Error("Bitcoin transaction not confirmed yet!");
|
|
1140
|
-
return await this._contract.txsClaimWithTxData(signer ?? this._getInitiator(), this._data, {
|
|
1141
|
-
blockhash: tx.blockhash,
|
|
1142
|
-
confirmations: tx.confirmations,
|
|
1143
|
-
txid: tx.txid,
|
|
1144
|
-
hex: tx.hex,
|
|
1145
|
-
height: tx.blockheight
|
|
1146
|
-
}, this.requiredConfirmations, this.vout, undefined, this.wrapper._synchronizer(this._contractVersion), true);
|
|
1147
|
-
}
|
|
1148
|
-
/**
|
|
1149
|
-
* Settles the swap by claiming the funds on the destination chain if the swap requires manual settlement, you can
|
|
1150
|
-
* check so with isClaimable.
|
|
1151
|
-
*
|
|
1152
|
-
* @remarks
|
|
1153
|
-
* Might also sync the bitcoin light client during the process.
|
|
1154
|
-
*
|
|
1155
|
-
* @param _signer Signer to use for signing the settlement transactions, can also be different to the recipient
|
|
1156
|
-
* @param abortSignal Abort signal
|
|
1157
|
-
* @param onBeforeTxSent Optional callback triggered before the claim transaction is broadcasted
|
|
1158
|
-
*
|
|
1159
|
-
* @returns Transaction ID of the settlement (claim) transaction on the destination smart chain
|
|
1160
|
-
*/
|
|
1161
|
-
async claim(_signer, abortSignal, onBeforeTxSent) {
|
|
1162
|
-
const signer = (0, base_1.isAbstractSigner)(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
|
|
1163
|
-
let txIds;
|
|
1164
|
-
try {
|
|
1165
|
-
let txCount = 0;
|
|
1166
|
-
const txs = await this.txsClaim(signer);
|
|
1167
|
-
txIds = await this.wrapper._chain.sendAndConfirm(signer, txs, true, abortSignal, undefined, (txId) => {
|
|
1168
|
-
txCount++;
|
|
1169
|
-
if (onBeforeTxSent != null && txCount === txs.length)
|
|
1170
|
-
onBeforeTxSent(txId);
|
|
1171
|
-
return Promise.resolve();
|
|
1172
|
-
});
|
|
1173
|
-
}
|
|
1174
|
-
catch (e) {
|
|
1175
|
-
this.logger.info("claim(): Failed to claim ourselves, checking swap claim state...");
|
|
1176
|
-
if (this._state === FromBTCSwapState.CLAIM_CLAIMED) {
|
|
1177
|
-
this.logger.info("claim(): Transaction state is CLAIM_CLAIMED, swap was successfully claimed by the watchtower");
|
|
1178
|
-
return this._claimTxId;
|
|
1179
|
-
}
|
|
1180
|
-
const status = await this._contract.getCommitStatus(this._getInitiator(), this._data);
|
|
1181
|
-
if (status?.type === base_1.SwapCommitStateType.PAID) {
|
|
1182
|
-
this.logger.info("claim(): Transaction commit status is PAID, swap was successfully claimed by the watchtower");
|
|
1183
|
-
if (this._claimTxId == null)
|
|
1184
|
-
this._claimTxId = await status.getClaimTxId();
|
|
1185
|
-
const txId = buffer_1.Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
|
|
1186
|
-
await this._setBitcoinTxId(txId);
|
|
1187
|
-
await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
|
|
1188
|
-
return this._claimTxId;
|
|
1189
|
-
}
|
|
1190
|
-
throw e;
|
|
1191
|
-
}
|
|
1192
|
-
this._claimTxId = txIds[txIds.length - 1];
|
|
1193
|
-
if (this._state === FromBTCSwapState.CLAIM_COMMITED || this._state === FromBTCSwapState.BTC_TX_CONFIRMED ||
|
|
1194
|
-
this._state === FromBTCSwapState.EXPIRED || this._state === FromBTCSwapState.FAILED) {
|
|
1195
|
-
await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
|
|
1196
|
-
}
|
|
1197
|
-
return txIds[txIds.length - 1];
|
|
1198
|
-
}
|
|
1199
|
-
/**
|
|
1200
|
-
* @inheritDoc
|
|
1201
|
-
*
|
|
1202
|
-
* @throws {Error} If swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
|
|
1203
|
-
* @throws {Error} If the LP refunded sooner than we were able to claim
|
|
1204
|
-
*/
|
|
1205
|
-
async waitTillClaimed(maxWaitTimeSeconds, abortSignal, pollIntervalSeconds) {
|
|
1206
|
-
if (this._state === FromBTCSwapState.CLAIM_CLAIMED)
|
|
1207
|
-
return Promise.resolve(true);
|
|
1208
|
-
if (this._state !== FromBTCSwapState.BTC_TX_CONFIRMED)
|
|
1209
|
-
throw new Error("Invalid state (not BTC_TX_CONFIRMED)");
|
|
1210
|
-
const abortController = (0, Utils_1.extendAbortController)(abortSignal);
|
|
1211
|
-
let timedOut = false;
|
|
1212
|
-
if (maxWaitTimeSeconds != null) {
|
|
1213
|
-
const timeout = setTimeout(() => {
|
|
1214
|
-
timedOut = true;
|
|
1215
|
-
abortController.abort();
|
|
1216
|
-
}, maxWaitTimeSeconds * 1000);
|
|
1217
|
-
abortController.signal.addEventListener("abort", () => clearTimeout(timeout));
|
|
1218
|
-
}
|
|
1219
|
-
let res;
|
|
1220
|
-
try {
|
|
1221
|
-
res = await Promise.race([
|
|
1222
|
-
this.watchdogWaitTillResult(pollIntervalSeconds, abortController.signal),
|
|
1223
|
-
this.waitTillState(FromBTCSwapState.CLAIM_CLAIMED, "eq", abortController.signal).then(() => 0),
|
|
1224
|
-
this.waitTillState(FromBTCSwapState.FAILED, "eq", abortController.signal).then(() => 1),
|
|
1225
|
-
]);
|
|
1226
|
-
abortController.abort();
|
|
1227
|
-
}
|
|
1228
|
-
catch (e) {
|
|
1229
|
-
abortController.abort();
|
|
1230
|
-
if (timedOut)
|
|
1231
|
-
return false;
|
|
1232
|
-
throw e;
|
|
1233
|
-
}
|
|
1234
|
-
if (res === 0) {
|
|
1235
|
-
this.logger.debug("waitTillClaimed(): Resolved from state change (CLAIM_CLAIMED)");
|
|
1236
|
-
return true;
|
|
1237
|
-
}
|
|
1238
|
-
if (res === 1) {
|
|
1239
|
-
this.logger.debug("waitTillClaimed(): Resolved from state change (FAILED)");
|
|
1240
|
-
throw new Error("Offerer refunded during claiming");
|
|
1241
|
-
}
|
|
1242
|
-
this.logger.debug("waitTillClaimed(): Resolved from watchdog");
|
|
1243
|
-
if (res?.type === base_1.SwapCommitStateType.PAID) {
|
|
1244
|
-
if (this._state !== FromBTCSwapState.CLAIM_CLAIMED) {
|
|
1245
|
-
if (this._claimTxId == null)
|
|
1246
|
-
this._claimTxId = await res.getClaimTxId();
|
|
1247
|
-
const txId = buffer_1.Buffer.from(await res.getClaimResult(), "hex").reverse().toString("hex");
|
|
1248
|
-
await this._setBitcoinTxId(txId);
|
|
1249
|
-
await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
if (res?.type === base_1.SwapCommitStateType.NOT_COMMITED || res?.type === base_1.SwapCommitStateType.EXPIRED) {
|
|
1253
|
-
if (this._state !== FromBTCSwapState.CLAIM_CLAIMED &&
|
|
1254
|
-
this._state !== FromBTCSwapState.FAILED) {
|
|
1255
|
-
if (res.getRefundTxId != null)
|
|
1256
|
-
this._refundTxId = await res.getRefundTxId();
|
|
1257
|
-
await this._saveAndEmit(FromBTCSwapState.FAILED);
|
|
1258
|
-
}
|
|
1259
|
-
throw new Error("Swap expired while waiting for claim!");
|
|
1260
|
-
}
|
|
1261
|
-
return true;
|
|
1262
|
-
}
|
|
1263
|
-
//////////////////////////////
|
|
1264
|
-
//// Storage
|
|
1265
|
-
/**
|
|
1266
|
-
* @inheritDoc
|
|
1267
|
-
*/
|
|
1268
|
-
serialize() {
|
|
1269
|
-
return {
|
|
1270
|
-
...super.serialize(),
|
|
1271
|
-
address: this.address,
|
|
1272
|
-
amount: this.amount == null ? null : this.amount.toString(10),
|
|
1273
|
-
requiredConfirmations: this.requiredConfirmations,
|
|
1274
|
-
senderAddress: this.senderAddress,
|
|
1275
|
-
txId: this.txId,
|
|
1276
|
-
vout: this.vout,
|
|
1277
|
-
btcTxConfirmedAt: this.btcTxConfirmedAt
|
|
1278
|
-
};
|
|
1279
|
-
}
|
|
1280
|
-
//////////////////////////////
|
|
1281
|
-
//// Swap ticks & sync
|
|
1282
|
-
/**
|
|
1283
|
-
* Checks the swap's state on-chain and compares it to its internal state, updates/changes it according to on-chain
|
|
1284
|
-
* data
|
|
1285
|
-
*
|
|
1286
|
-
* @private
|
|
1287
|
-
*/
|
|
1288
|
-
async syncStateFromChain(quoteDefinitelyExpired, commitStatus) {
|
|
1289
|
-
if (this._state === FromBTCSwapState.PR_CREATED ||
|
|
1290
|
-
this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
|
|
1291
|
-
this._state === FromBTCSwapState.CLAIM_COMMITED ||
|
|
1292
|
-
this._state === FromBTCSwapState.BTC_TX_CONFIRMED ||
|
|
1293
|
-
this._state === FromBTCSwapState.EXPIRED) {
|
|
1294
|
-
let quoteExpired = false;
|
|
1295
|
-
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1296
|
-
quoteExpired = quoteDefinitelyExpired ?? await this._verifyQuoteDefinitelyExpired(); //Make sure we check for expiry here, to prevent race conditions
|
|
1297
|
-
}
|
|
1298
|
-
const status = commitStatus ?? await this._contract.getCommitStatus(this._getInitiator(), this._data);
|
|
1299
|
-
if (status != null && await this._forciblySetOnchainState(status))
|
|
1300
|
-
return true;
|
|
1301
|
-
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1302
|
-
if (quoteExpired) {
|
|
1303
|
-
this._state = FromBTCSwapState.QUOTE_EXPIRED;
|
|
1304
|
-
return true;
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
return false;
|
|
1309
|
-
}
|
|
1310
|
-
/**
|
|
1311
|
-
* @inheritDoc
|
|
1312
|
-
* @internal
|
|
1313
|
-
*/
|
|
1314
|
-
_shouldFetchOnchainState() {
|
|
1315
|
-
return this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
|
|
1316
|
-
this._state === FromBTCSwapState.CLAIM_COMMITED || this._state === FromBTCSwapState.BTC_TX_CONFIRMED ||
|
|
1317
|
-
this._state === FromBTCSwapState.EXPIRED;
|
|
1318
|
-
}
|
|
1319
|
-
/**
|
|
1320
|
-
* @inheritDoc
|
|
1321
|
-
* @internal
|
|
1322
|
-
*/
|
|
1323
|
-
_shouldFetchExpiryStatus() {
|
|
1324
|
-
return this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED;
|
|
1325
|
-
}
|
|
1326
|
-
/**
|
|
1327
|
-
* @inheritDoc
|
|
1328
|
-
* @internal
|
|
1329
|
-
*/
|
|
1330
|
-
async _sync(save, quoteDefinitelyExpired, commitStatus) {
|
|
1331
|
-
const changed = await this.syncStateFromChain(quoteDefinitelyExpired, commitStatus);
|
|
1332
|
-
if (changed && save)
|
|
1333
|
-
await this._saveAndEmit();
|
|
1334
|
-
return changed;
|
|
1335
|
-
}
|
|
1336
|
-
/**
|
|
1337
|
-
* @inheritDoc
|
|
1338
|
-
* @internal
|
|
1339
|
-
*/
|
|
1340
|
-
async _forciblySetOnchainState(status) {
|
|
1341
|
-
switch (status.type) {
|
|
1342
|
-
case base_1.SwapCommitStateType.PAID:
|
|
1343
|
-
if (this._commitTxId == null && status.getInitTxId != null)
|
|
1344
|
-
this._commitTxId = await status.getInitTxId();
|
|
1345
|
-
if (this._claimTxId == null)
|
|
1346
|
-
this._claimTxId = await status.getClaimTxId();
|
|
1347
|
-
const txId = buffer_1.Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
|
|
1348
|
-
await this._setBitcoinTxId(txId);
|
|
1349
|
-
this._state = FromBTCSwapState.CLAIM_CLAIMED;
|
|
1350
|
-
return true;
|
|
1351
|
-
case base_1.SwapCommitStateType.NOT_COMMITED:
|
|
1352
|
-
let changed = false;
|
|
1353
|
-
if (this._commitTxId == null && status.getInitTxId != null) {
|
|
1354
|
-
this._commitTxId = await status.getInitTxId();
|
|
1355
|
-
changed = true;
|
|
1356
|
-
}
|
|
1357
|
-
if (this._refundTxId == null && status.getRefundTxId) {
|
|
1358
|
-
this._refundTxId = await status.getRefundTxId();
|
|
1359
|
-
changed = true;
|
|
1360
|
-
}
|
|
1361
|
-
if (this._refundTxId != null) {
|
|
1362
|
-
this._state = FromBTCSwapState.FAILED;
|
|
1363
|
-
changed = true;
|
|
1364
|
-
}
|
|
1365
|
-
return changed;
|
|
1366
|
-
case base_1.SwapCommitStateType.EXPIRED:
|
|
1367
|
-
if (this._commitTxId == null && status.getInitTxId != null)
|
|
1368
|
-
this._commitTxId = await status.getInitTxId();
|
|
1369
|
-
if (this._refundTxId == null && status.getRefundTxId)
|
|
1370
|
-
this._refundTxId = await status.getRefundTxId();
|
|
1371
|
-
this._state = this._refundTxId == null ? FromBTCSwapState.QUOTE_EXPIRED : FromBTCSwapState.FAILED;
|
|
1372
|
-
return true;
|
|
1373
|
-
case base_1.SwapCommitStateType.COMMITED:
|
|
1374
|
-
let save = false;
|
|
1375
|
-
if (this._commitTxId == null && status.getInitTxId != null) {
|
|
1376
|
-
this._commitTxId = await status.getInitTxId();
|
|
1377
|
-
save = true;
|
|
1378
|
-
}
|
|
1379
|
-
if (this._state !== FromBTCSwapState.CLAIM_COMMITED && this._state !== FromBTCSwapState.BTC_TX_CONFIRMED && this._state !== FromBTCSwapState.EXPIRED) {
|
|
1380
|
-
this._state = FromBTCSwapState.CLAIM_COMMITED;
|
|
1381
|
-
save = true;
|
|
1382
|
-
}
|
|
1383
|
-
if (this.address == null)
|
|
1384
|
-
return save;
|
|
1385
|
-
this.btcTxLastChecked = Date.now();
|
|
1386
|
-
const res = await this.getBitcoinPayment();
|
|
1387
|
-
if (res != null) {
|
|
1388
|
-
if (this.txId !== res.txId || this.vout !== res.vout || (res.inputAddresses != null && this.senderAddress == null)) {
|
|
1389
|
-
if (res.inputAddresses != null)
|
|
1390
|
-
this.senderAddress = res.inputAddresses[0];
|
|
1391
|
-
this.txId = res.txId;
|
|
1392
|
-
this.vout = res.vout;
|
|
1393
|
-
save = true;
|
|
1394
|
-
}
|
|
1395
|
-
if (this.requiredConfirmations != null && res.confirmations >= this.requiredConfirmations) {
|
|
1396
|
-
this.btcTxConfirmedAt ??= Date.now();
|
|
1397
|
-
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
1398
|
-
save = true;
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
return save;
|
|
1402
|
-
}
|
|
1403
|
-
return false;
|
|
1404
|
-
}
|
|
1405
|
-
/**
|
|
1406
|
-
* @inheritDoc
|
|
1407
|
-
* @internal
|
|
1408
|
-
*/
|
|
1409
|
-
async _tick(save) {
|
|
1410
|
-
switch (this._state) {
|
|
1411
|
-
case FromBTCSwapState.PR_CREATED:
|
|
1412
|
-
if (this.expiry < Date.now()) {
|
|
1413
|
-
this._state = FromBTCSwapState.QUOTE_SOFT_EXPIRED;
|
|
1414
|
-
if (save)
|
|
1415
|
-
await this._saveAndEmit();
|
|
1416
|
-
return true;
|
|
1417
|
-
}
|
|
1418
|
-
break;
|
|
1419
|
-
case FromBTCSwapState.CLAIM_COMMITED:
|
|
1420
|
-
if (this.getTimeoutTime() < Date.now()) {
|
|
1421
|
-
this._state = FromBTCSwapState.EXPIRED;
|
|
1422
|
-
if (save)
|
|
1423
|
-
await this._saveAndEmit();
|
|
1424
|
-
return true;
|
|
1425
|
-
}
|
|
1426
|
-
case FromBTCSwapState.EXPIRED:
|
|
1427
|
-
//Check if bitcoin payment was received at least every 2 minutes
|
|
1428
|
-
if (this.btcTxLastChecked == null || Date.now() - this.btcTxLastChecked > 120000) {
|
|
1429
|
-
if (this.address != null)
|
|
1430
|
-
try {
|
|
1431
|
-
this.btcTxLastChecked = Date.now();
|
|
1432
|
-
const res = await this.getBitcoinPayment();
|
|
1433
|
-
if (res != null) {
|
|
1434
|
-
let shouldSave = false;
|
|
1435
|
-
if (this.txId !== res.txId || this.vout !== res.vout || (res.inputAddresses != null && this.senderAddress == null)) {
|
|
1436
|
-
this.txId = res.txId;
|
|
1437
|
-
this.vout = res.vout;
|
|
1438
|
-
if (res.inputAddresses != null)
|
|
1439
|
-
this.senderAddress = res.inputAddresses[0];
|
|
1440
|
-
shouldSave = true;
|
|
1441
|
-
}
|
|
1442
|
-
if (this.requiredConfirmations != null && res.confirmations >= this.requiredConfirmations) {
|
|
1443
|
-
this.btcTxConfirmedAt ??= Date.now();
|
|
1444
|
-
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
1445
|
-
if (save)
|
|
1446
|
-
await this._saveAndEmit();
|
|
1447
|
-
shouldSave = true;
|
|
1448
|
-
}
|
|
1449
|
-
if (shouldSave && save)
|
|
1450
|
-
await this._saveAndEmit();
|
|
1451
|
-
return shouldSave;
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
catch (e) {
|
|
1455
|
-
this.logger.warn("tickSwap(" + this.getIdentifierHashString() + "): ", e);
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
break;
|
|
1459
|
-
}
|
|
1460
|
-
return false;
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
|
-
exports.FromBTCSwap = FromBTCSwap;
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FromBTCSwap = exports.isFromBTCSwapInit = exports.FromBTCSwapState = void 0;
|
|
4
|
+
const IFromBTCSelfInitSwap_1 = require("../IFromBTCSelfInitSwap");
|
|
5
|
+
const SwapType_1 = require("../../../../enums/SwapType");
|
|
6
|
+
const base_1 = require("@atomiqlabs/base");
|
|
7
|
+
const buffer_1 = require("buffer");
|
|
8
|
+
const Utils_1 = require("../../../../utils/Utils");
|
|
9
|
+
const BitcoinUtils_1 = require("../../../../utils/BitcoinUtils");
|
|
10
|
+
const IBitcoinWallet_1 = require("../../../../bitcoin/wallet/IBitcoinWallet");
|
|
11
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
12
|
+
const SingleAddressBitcoinWallet_1 = require("../../../../bitcoin/wallet/SingleAddressBitcoinWallet");
|
|
13
|
+
const IEscrowSelfInitSwap_1 = require("../../IEscrowSelfInitSwap");
|
|
14
|
+
const TokenAmount_1 = require("../../../../types/TokenAmount");
|
|
15
|
+
const Token_1 = require("../../../../types/Token");
|
|
16
|
+
const Logger_1 = require("../../../../utils/Logger");
|
|
17
|
+
const BitcoinWalletUtils_1 = require("../../../../utils/BitcoinWalletUtils");
|
|
18
|
+
/**
|
|
19
|
+
* State enum for legacy escrow based Bitcoin -> Smart chain swaps.
|
|
20
|
+
*
|
|
21
|
+
* @category Swaps/Legacy/Bitcoin → Smart chain
|
|
22
|
+
*/
|
|
23
|
+
var FromBTCSwapState;
|
|
24
|
+
(function (FromBTCSwapState) {
|
|
25
|
+
/**
|
|
26
|
+
* Bitcoin swap address has expired and the intermediary (LP) has already refunded
|
|
27
|
+
* its funds. No BTC should be sent anymore!
|
|
28
|
+
*/
|
|
29
|
+
FromBTCSwapState[FromBTCSwapState["FAILED"] = -4] = "FAILED";
|
|
30
|
+
/**
|
|
31
|
+
* Bitcoin swap address has expired, user should not send any BTC anymore! Though
|
|
32
|
+
* the intermediary (LP) hasn't refunded yet. So if there is a transaction already
|
|
33
|
+
* in-flight the swap might still succeed.
|
|
34
|
+
*/
|
|
35
|
+
FromBTCSwapState[FromBTCSwapState["EXPIRED"] = -3] = "EXPIRED";
|
|
36
|
+
/**
|
|
37
|
+
* Swap has expired for good and there is no way how it can be executed anymore
|
|
38
|
+
*/
|
|
39
|
+
FromBTCSwapState[FromBTCSwapState["QUOTE_EXPIRED"] = -2] = "QUOTE_EXPIRED";
|
|
40
|
+
/**
|
|
41
|
+
* A swap is almost expired, and it should be presented to the user as expired, though
|
|
42
|
+
* there is still a chance that it will be processed
|
|
43
|
+
*/
|
|
44
|
+
FromBTCSwapState[FromBTCSwapState["QUOTE_SOFT_EXPIRED"] = -1] = "QUOTE_SOFT_EXPIRED";
|
|
45
|
+
/**
|
|
46
|
+
* Swap quote was created, use the {@link FromBTCSwap.commit} or {@link FromBTCSwap.txsCommit} functions
|
|
47
|
+
* to initiate it by creating the swap escrow on the destination smart chain
|
|
48
|
+
*/
|
|
49
|
+
FromBTCSwapState[FromBTCSwapState["PR_CREATED"] = 0] = "PR_CREATED";
|
|
50
|
+
/**
|
|
51
|
+
* Swap escrow was initiated (committed) on the destination chain, user can send the BTC to the
|
|
52
|
+
* swap address with the {@link FromBTCSwap.getFundedPsbt}, {@link FromBTCSwap.getAddress} or
|
|
53
|
+
* {@link FromBTCSwap.getHyperlink} functions.
|
|
54
|
+
*/
|
|
55
|
+
FromBTCSwapState[FromBTCSwapState["CLAIM_COMMITED"] = 1] = "CLAIM_COMMITED";
|
|
56
|
+
/**
|
|
57
|
+
* Input bitcoin transaction was confirmed, wait for automatic settlement by the watchtowers
|
|
58
|
+
* using the {@link FromBTCSwap.waitTillClaimed} function or settle manually using the {@link FromBTCSwap.claim}
|
|
59
|
+
* or {@link FromBTCSwap.txsClaim} function.
|
|
60
|
+
*/
|
|
61
|
+
FromBTCSwapState[FromBTCSwapState["BTC_TX_CONFIRMED"] = 2] = "BTC_TX_CONFIRMED";
|
|
62
|
+
/**
|
|
63
|
+
* Swap successfully settled and funds received on the destination chain
|
|
64
|
+
*/
|
|
65
|
+
FromBTCSwapState[FromBTCSwapState["CLAIM_CLAIMED"] = 3] = "CLAIM_CLAIMED";
|
|
66
|
+
})(FromBTCSwapState = exports.FromBTCSwapState || (exports.FromBTCSwapState = {}));
|
|
67
|
+
const FromBTCSwapStateDescription = {
|
|
68
|
+
[FromBTCSwapState.FAILED]: "Bitcoin swap address has expired and the intermediary (LP) has already refunded its funds. No BTC should be sent anymore!",
|
|
69
|
+
[FromBTCSwapState.EXPIRED]: "Bitcoin swap address has expired, user should not send any BTC anymore! Though the intermediary (LP) hasn't refunded yet. So if there is a transaction already in-flight the swap might still succeed.",
|
|
70
|
+
[FromBTCSwapState.QUOTE_EXPIRED]: "Swap has expired for good and there is no way how it can be executed anymore",
|
|
71
|
+
[FromBTCSwapState.QUOTE_SOFT_EXPIRED]: "The swap is expired, though there is still a chance that it will be processed",
|
|
72
|
+
[FromBTCSwapState.PR_CREATED]: "Swap quote was created, initiate it by creating the swap escrow on the destination smart chain",
|
|
73
|
+
[FromBTCSwapState.CLAIM_COMMITED]: "Swap escrow was initiated (committed) on the destination chain, user can send the BTC to the Bitcoin swap address.",
|
|
74
|
+
[FromBTCSwapState.BTC_TX_CONFIRMED]: "Input bitcoin transaction was confirmed, wait for automatic settlement by the watchtower or settle manually.",
|
|
75
|
+
[FromBTCSwapState.CLAIM_CLAIMED]: "Swap successfully settled and funds received on the destination chain"
|
|
76
|
+
};
|
|
77
|
+
function isFromBTCSwapInit(obj) {
|
|
78
|
+
return typeof (obj.data) === "object" &&
|
|
79
|
+
(obj.address == null || typeof (obj.address) === "string") &&
|
|
80
|
+
(obj.amount == null || typeof (obj.amount) === "bigint") &&
|
|
81
|
+
(obj.requiredConfirmations == null || typeof (obj.requiredConfirmations) === "number") &&
|
|
82
|
+
(0, IEscrowSelfInitSwap_1.isIEscrowSelfInitSwapInit)(obj);
|
|
83
|
+
}
|
|
84
|
+
exports.isFromBTCSwapInit = isFromBTCSwapInit;
|
|
85
|
+
/**
|
|
86
|
+
* Legacy escrow (PrTLC) based swap for Bitcoin -> Smart chains, requires manual initiation
|
|
87
|
+
* of the swap escrow on the destination chain.
|
|
88
|
+
*
|
|
89
|
+
* @category Swaps/Legacy/Bitcoin → Smart chain
|
|
90
|
+
*/
|
|
91
|
+
class FromBTCSwap extends IFromBTCSelfInitSwap_1.IFromBTCSelfInitSwap {
|
|
92
|
+
constructor(wrapper, initOrObject) {
|
|
93
|
+
if (isFromBTCSwapInit(initOrObject) && initOrObject.url != null)
|
|
94
|
+
initOrObject.url += "/frombtc";
|
|
95
|
+
super(wrapper, initOrObject);
|
|
96
|
+
this.TYPE = SwapType_1.SwapType.FROM_BTC;
|
|
97
|
+
/**
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
this.swapStateName = (state) => FromBTCSwapState[state];
|
|
101
|
+
/**
|
|
102
|
+
* @internal
|
|
103
|
+
*/
|
|
104
|
+
this.swapStateDescription = FromBTCSwapStateDescription;
|
|
105
|
+
/**
|
|
106
|
+
* @internal
|
|
107
|
+
*/
|
|
108
|
+
this.inputToken = Token_1.BitcoinTokens.BTC;
|
|
109
|
+
if (isFromBTCSwapInit(initOrObject)) {
|
|
110
|
+
this._state = FromBTCSwapState.PR_CREATED;
|
|
111
|
+
this._data = initOrObject.data;
|
|
112
|
+
this.feeRate = initOrObject.feeRate;
|
|
113
|
+
this.address = initOrObject.address;
|
|
114
|
+
this.amount = initOrObject.amount;
|
|
115
|
+
this.requiredConfirmations = initOrObject.requiredConfirmations;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
this.address = initOrObject.address;
|
|
119
|
+
this.amount = (0, Utils_1.toBigInt)(initOrObject.amount);
|
|
120
|
+
this.senderAddress = initOrObject.senderAddress;
|
|
121
|
+
this.txId = initOrObject.txId;
|
|
122
|
+
this.vout = initOrObject.vout;
|
|
123
|
+
this.requiredConfirmations = initOrObject.requiredConfirmations ?? this._data.getConfirmationsHint();
|
|
124
|
+
this.btcTxConfirmedAt = initOrObject.btcTxConfirmedAt;
|
|
125
|
+
}
|
|
126
|
+
this.tryRecomputeSwapPrice();
|
|
127
|
+
this.logger = (0, Logger_1.getLogger)("FromBTC(" + this.getIdentifierHashString() + "): ");
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* @inheritDoc
|
|
131
|
+
* @internal
|
|
132
|
+
*/
|
|
133
|
+
getSwapData() {
|
|
134
|
+
return this._data;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* @inheritDoc
|
|
138
|
+
* @internal
|
|
139
|
+
*/
|
|
140
|
+
upgradeVersion() {
|
|
141
|
+
if (this.version == null) {
|
|
142
|
+
switch (this._state) {
|
|
143
|
+
case -2:
|
|
144
|
+
this._state = FromBTCSwapState.FAILED;
|
|
145
|
+
break;
|
|
146
|
+
case -1:
|
|
147
|
+
this._state = FromBTCSwapState.QUOTE_EXPIRED;
|
|
148
|
+
break;
|
|
149
|
+
case 0:
|
|
150
|
+
this._state = FromBTCSwapState.PR_CREATED;
|
|
151
|
+
break;
|
|
152
|
+
case 1:
|
|
153
|
+
this._state = FromBTCSwapState.CLAIM_COMMITED;
|
|
154
|
+
break;
|
|
155
|
+
case 2:
|
|
156
|
+
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
157
|
+
break;
|
|
158
|
+
case 3:
|
|
159
|
+
this._state = FromBTCSwapState.CLAIM_CLAIMED;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
this.version = 1;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
//////////////////////////////
|
|
166
|
+
//// Getters & utils
|
|
167
|
+
/**
|
|
168
|
+
* Returns bitcoin address where the on-chain BTC should be sent to
|
|
169
|
+
*/
|
|
170
|
+
getAddress() {
|
|
171
|
+
if (this._state === FromBTCSwapState.PR_CREATED)
|
|
172
|
+
throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
|
|
173
|
+
return this.address ?? "";
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Unsafe bitcoin hyperlink getter, returns the address even before the swap is committed!
|
|
177
|
+
*
|
|
178
|
+
* @private
|
|
179
|
+
*/
|
|
180
|
+
_getHyperlink() {
|
|
181
|
+
return this.address == null || this.amount == null ? "" : "bitcoin:" + this.address + "?amount=" + encodeURIComponent((Number(this.amount) / 100000000).toString(10));
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* @inheritDoc
|
|
185
|
+
*/
|
|
186
|
+
getHyperlink() {
|
|
187
|
+
if (this._state === FromBTCSwapState.PR_CREATED)
|
|
188
|
+
throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
|
|
189
|
+
return this._getHyperlink();
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* @inheritDoc
|
|
193
|
+
*/
|
|
194
|
+
getInputAddress() {
|
|
195
|
+
return this.senderAddress ?? null;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* @inheritDoc
|
|
199
|
+
*/
|
|
200
|
+
getInputTxId() {
|
|
201
|
+
return this.txId ?? null;
|
|
202
|
+
}
|
|
203
|
+
async _setSubmittedBitcoinTx(txId, psbt) {
|
|
204
|
+
let changed = false;
|
|
205
|
+
if (this.txId !== txId) {
|
|
206
|
+
this.txId = txId;
|
|
207
|
+
changed = true;
|
|
208
|
+
}
|
|
209
|
+
const submittedVout = this.address == null || this.amount == null || psbt == null
|
|
210
|
+
? undefined
|
|
211
|
+
: (0, BitcoinUtils_1.getVoutIndex)(psbt, this.wrapper._options.bitcoinNetwork, this.address, this.amount);
|
|
212
|
+
if (submittedVout != null && this.vout !== submittedVout) {
|
|
213
|
+
this.vout = submittedVout;
|
|
214
|
+
changed = true;
|
|
215
|
+
}
|
|
216
|
+
const submittedSenderAddress = psbt == null
|
|
217
|
+
? undefined
|
|
218
|
+
: (0, BitcoinUtils_1.getSenderAddress)(psbt, this.wrapper._options.bitcoinNetwork);
|
|
219
|
+
if (submittedSenderAddress != null && this.senderAddress !== submittedSenderAddress) {
|
|
220
|
+
this.senderAddress = submittedSenderAddress;
|
|
221
|
+
changed = true;
|
|
222
|
+
}
|
|
223
|
+
if (changed)
|
|
224
|
+
await this._saveAndEmit();
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Returns timeout time (in UNIX milliseconds) when the on-chain address will expire and no funds should be sent
|
|
228
|
+
* to that address anymore
|
|
229
|
+
*/
|
|
230
|
+
getTimeoutTime() {
|
|
231
|
+
return Number(this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations ?? 6)) * 1000;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* @inheritDoc
|
|
235
|
+
*/
|
|
236
|
+
requiresAction() {
|
|
237
|
+
return this.isClaimable() || (this._state === FromBTCSwapState.CLAIM_COMMITED && this.getTimeoutTime() > Date.now() && this.txId == null);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* @inheritDoc
|
|
241
|
+
*/
|
|
242
|
+
isFinished() {
|
|
243
|
+
return this._state === FromBTCSwapState.CLAIM_CLAIMED || this._state === FromBTCSwapState.QUOTE_EXPIRED || this._state === FromBTCSwapState.FAILED;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* @inheritDoc
|
|
247
|
+
*/
|
|
248
|
+
isClaimable() {
|
|
249
|
+
return this._state === FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* @inheritDoc
|
|
253
|
+
*/
|
|
254
|
+
isSuccessful() {
|
|
255
|
+
return this._state === FromBTCSwapState.CLAIM_CLAIMED;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* @inheritDoc
|
|
259
|
+
*/
|
|
260
|
+
isFailed() {
|
|
261
|
+
return this._state === FromBTCSwapState.FAILED || this._state === FromBTCSwapState.EXPIRED;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* @inheritDoc
|
|
265
|
+
*/
|
|
266
|
+
isInProgress() {
|
|
267
|
+
return this._state === FromBTCSwapState.CLAIM_COMMITED ||
|
|
268
|
+
this._state === FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* @inheritDoc
|
|
272
|
+
*/
|
|
273
|
+
isQuoteExpired() {
|
|
274
|
+
return this._state === FromBTCSwapState.QUOTE_EXPIRED;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* @inheritDoc
|
|
278
|
+
*/
|
|
279
|
+
isQuoteSoftExpired() {
|
|
280
|
+
return this._state === FromBTCSwapState.QUOTE_EXPIRED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* @inheritDoc
|
|
284
|
+
* @internal
|
|
285
|
+
*/
|
|
286
|
+
canCommit(skipQuoteExpiryChecks) {
|
|
287
|
+
if (this._state !== FromBTCSwapState.PR_CREATED && (!skipQuoteExpiryChecks || this._state !== FromBTCSwapState.QUOTE_SOFT_EXPIRED))
|
|
288
|
+
return false;
|
|
289
|
+
if (this.requiredConfirmations == null)
|
|
290
|
+
return false;
|
|
291
|
+
const expiry = this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations);
|
|
292
|
+
const currentTimestamp = BigInt(Math.floor(Date.now() / 1000));
|
|
293
|
+
return (expiry - currentTimestamp) >= this.wrapper._options.minSendWindow;
|
|
294
|
+
}
|
|
295
|
+
//////////////////////////////
|
|
296
|
+
//// Amounts & fees
|
|
297
|
+
/**
|
|
298
|
+
* @inheritDoc
|
|
299
|
+
*/
|
|
300
|
+
getInputToken() {
|
|
301
|
+
return Token_1.BitcoinTokens.BTC;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* @inheritDoc
|
|
305
|
+
*/
|
|
306
|
+
getInput() {
|
|
307
|
+
return (0, TokenAmount_1.toTokenAmount)(this.amount ?? null, this.inputToken, this.wrapper._prices);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Returns claimer bounty, acting as a reward for watchtowers to claim the swap automatically,
|
|
311
|
+
* this amount is pre-funded by the user on the destination chain when the swap escrow
|
|
312
|
+
* is initiated. For total pre-funded deposit amount see {@link getTotalDeposit}.
|
|
313
|
+
*/
|
|
314
|
+
getClaimerBounty() {
|
|
315
|
+
return (0, TokenAmount_1.toTokenAmount)(this._data.getClaimerBounty(), this.wrapper._tokens[this._data.getDepositToken()], this.wrapper._prices);
|
|
316
|
+
}
|
|
317
|
+
//////////////////////////////
|
|
318
|
+
//// Bitcoin tx
|
|
319
|
+
/**
|
|
320
|
+
* If the required number of confirmations is not known, this function tries to infer it by looping through
|
|
321
|
+
* possible confirmation targets and comparing the claim hashes
|
|
322
|
+
*
|
|
323
|
+
* @param btcTx Bitcoin transaction
|
|
324
|
+
* @param vout Output index of the desired output in the bitcoin transaction
|
|
325
|
+
*
|
|
326
|
+
* @private
|
|
327
|
+
*/
|
|
328
|
+
inferRequiredConfirmationsCount(btcTx, vout) {
|
|
329
|
+
const txOut = btcTx.outs[vout];
|
|
330
|
+
for (let i = 1; i <= 20; i++) {
|
|
331
|
+
const computedClaimHash = this._contract.getHashForOnchain(buffer_1.Buffer.from(txOut.scriptPubKey.hex, "hex"), BigInt(txOut.value), i);
|
|
332
|
+
if (computedClaimHash.toString("hex") === this._data.getClaimHash()) {
|
|
333
|
+
return i;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* @inheritDoc
|
|
339
|
+
*/
|
|
340
|
+
getRequiredConfirmationsCount() {
|
|
341
|
+
return this.requiredConfirmations ?? NaN;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Checks whether a bitcoin payment was already made, returns the payment or `null` when no payment has been made.
|
|
345
|
+
*
|
|
346
|
+
* @internal
|
|
347
|
+
*/
|
|
348
|
+
async getBitcoinPayment() {
|
|
349
|
+
const txoHashHint = this._data.getTxoHashHint();
|
|
350
|
+
if (txoHashHint == null)
|
|
351
|
+
throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
|
|
352
|
+
if (this.address == null)
|
|
353
|
+
throw new Error("Cannot check bitcoin payment, because the address is not known! This can happen after a swap is recovered.");
|
|
354
|
+
const result = await this.wrapper._btcRpc.checkAddressTxos(this.address, buffer_1.Buffer.from(txoHashHint, "hex"));
|
|
355
|
+
if (result == null)
|
|
356
|
+
return null;
|
|
357
|
+
if (this.requiredConfirmations == null) {
|
|
358
|
+
this.requiredConfirmations = this.inferRequiredConfirmationsCount(result.tx, result.vout);
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
inputAddresses: result.tx.inputAddresses,
|
|
362
|
+
txId: result.tx.txid,
|
|
363
|
+
vout: result.vout,
|
|
364
|
+
confirmations: result.tx.confirmations ?? 0,
|
|
365
|
+
targetConfirmations: this.getRequiredConfirmationsCount()
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Used to set the txId of the bitcoin payment from the on-chain events listener
|
|
370
|
+
*
|
|
371
|
+
* @param txId Transaction ID that settled the swap on the smart chain
|
|
372
|
+
*
|
|
373
|
+
* @internal
|
|
374
|
+
*/
|
|
375
|
+
async _setBitcoinTxId(txId) {
|
|
376
|
+
if (this.txId !== txId || this.address == null || this.vout == null || this.senderAddress == null || this.amount == null) {
|
|
377
|
+
const btcTx = await this.wrapper._btcRpc.getTransaction(txId);
|
|
378
|
+
if (btcTx == null)
|
|
379
|
+
return;
|
|
380
|
+
const txoHashHint = this._data.getTxoHashHint();
|
|
381
|
+
if (txoHashHint != null) {
|
|
382
|
+
const expectedTxoHash = buffer_1.Buffer.from(txoHashHint, "hex");
|
|
383
|
+
const vout = btcTx.outs.findIndex(out => (0, Utils_1.getTxoHash)(out.scriptPubKey.hex, out.value).equals(expectedTxoHash));
|
|
384
|
+
if (vout !== -1) {
|
|
385
|
+
this.vout = vout;
|
|
386
|
+
//If amount or address are not known, parse them from the bitcoin tx
|
|
387
|
+
// this can happen if the swap is recovered from on-chain data and
|
|
388
|
+
// hence doesn't contain the address and amount data
|
|
389
|
+
if (this.amount == null)
|
|
390
|
+
this.amount = BigInt(btcTx.outs[vout].value);
|
|
391
|
+
if (this.address == null)
|
|
392
|
+
try {
|
|
393
|
+
this.address = (0, BitcoinUtils_1.fromOutputScript)(this.wrapper._options.bitcoinNetwork, btcTx.outs[vout].scriptPubKey.hex);
|
|
394
|
+
}
|
|
395
|
+
catch (e) {
|
|
396
|
+
this.logger.warn("_setBitcoinTxId(): Failed to parse address from output script: ", e);
|
|
397
|
+
}
|
|
398
|
+
if (this.requiredConfirmations == null) {
|
|
399
|
+
this.requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (btcTx.inputAddresses != null) {
|
|
404
|
+
this.senderAddress = btcTx.inputAddresses[0];
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
this.txId = txId;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* @inheritDoc
|
|
411
|
+
*
|
|
412
|
+
* @throws {Error} if in invalid state (must be {@link FromBTCSwapState.CLAIM_COMMITED})
|
|
413
|
+
*/
|
|
414
|
+
async waitForBitcoinTransaction(updateCallback, checkIntervalSeconds, abortSignal) {
|
|
415
|
+
if (this._state !== FromBTCSwapState.CLAIM_COMMITED && this._state !== FromBTCSwapState.EXPIRED)
|
|
416
|
+
throw new Error("Must be in COMMITED state!");
|
|
417
|
+
const txoHashHint = this._data.getTxoHashHint();
|
|
418
|
+
if (txoHashHint == null)
|
|
419
|
+
throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
|
|
420
|
+
if (this.address == null)
|
|
421
|
+
throw new Error("Cannot check bitcoin payment, because the address is not known! This can happen after a swap is recovered.");
|
|
422
|
+
let abortedDueToEnoughConfirmationsResult;
|
|
423
|
+
const abortController = (0, Utils_1.extendAbortController)(abortSignal);
|
|
424
|
+
const result = await this.wrapper._btcRpc.waitForAddressTxo(this.address, buffer_1.Buffer.from(txoHashHint, "hex"), this.requiredConfirmations ?? 6, //In case confirmation count is not known, we use a conservative estimate
|
|
425
|
+
(btcTx, vout, txEtaMs) => {
|
|
426
|
+
let requiredConfirmations = this.requiredConfirmations;
|
|
427
|
+
if (btcTx != null && vout != null && requiredConfirmations == null) {
|
|
428
|
+
requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
|
|
429
|
+
}
|
|
430
|
+
if (btcTx != null && (btcTx.txid !== this.txId ||
|
|
431
|
+
this.vout == null ||
|
|
432
|
+
this.senderAddress == null ||
|
|
433
|
+
(this.requiredConfirmations == null && requiredConfirmations != null))) {
|
|
434
|
+
this.txId = btcTx.txid;
|
|
435
|
+
this.vout = vout;
|
|
436
|
+
this.requiredConfirmations = requiredConfirmations;
|
|
437
|
+
if (btcTx.inputAddresses != null)
|
|
438
|
+
this.senderAddress = btcTx.inputAddresses[0];
|
|
439
|
+
this._saveAndEmit().catch(e => {
|
|
440
|
+
this.logger.error("waitForBitcoinTransaction(): Failed to save swap from within waitForAddressTxo callback:", e);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
//Abort the loop as soon as the transaction gets enough confirmations, this is required in case
|
|
444
|
+
// we pass a default 6 confirmations to the fn, but then are able to infer the actual confirmation
|
|
445
|
+
// target from the prior block
|
|
446
|
+
if (btcTx?.confirmations != null && requiredConfirmations != null && requiredConfirmations <= btcTx.confirmations && vout != null) {
|
|
447
|
+
abortedDueToEnoughConfirmationsResult = {
|
|
448
|
+
tx: btcTx,
|
|
449
|
+
vout
|
|
450
|
+
};
|
|
451
|
+
abortController.abort();
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (updateCallback != null)
|
|
455
|
+
updateCallback(btcTx?.txid, btcTx == null ? undefined : (btcTx?.confirmations ?? 0), requiredConfirmations ?? NaN, txEtaMs);
|
|
456
|
+
}, abortController.signal, checkIntervalSeconds).catch(e => {
|
|
457
|
+
//We catch the case when the loop was aborted due to the transaction getting enough confirmations
|
|
458
|
+
if (abortedDueToEnoughConfirmationsResult != null)
|
|
459
|
+
return abortedDueToEnoughConfirmationsResult;
|
|
460
|
+
throw e;
|
|
461
|
+
});
|
|
462
|
+
if (abortSignal != null)
|
|
463
|
+
abortSignal.throwIfAborted();
|
|
464
|
+
this.txId = result.tx.txid;
|
|
465
|
+
this.vout = result.vout;
|
|
466
|
+
if (result.tx.inputAddresses != null)
|
|
467
|
+
this.senderAddress = result.tx.inputAddresses[0];
|
|
468
|
+
if (this._state !== FromBTCSwapState.CLAIM_CLAIMED &&
|
|
469
|
+
this._state !== FromBTCSwapState.FAILED) {
|
|
470
|
+
this.btcTxConfirmedAt ??= Date.now();
|
|
471
|
+
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
472
|
+
}
|
|
473
|
+
await this._saveAndEmit();
|
|
474
|
+
return result.tx.txid;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Private getter of the funded PSBT that doesn't check current state
|
|
478
|
+
*
|
|
479
|
+
* @param _bitcoinWallet Bitcoin wallet to fund the PSBT with
|
|
480
|
+
* @param feeRate Optional bitcoin fee rate in sats/vB
|
|
481
|
+
* @param additionalOutputs Optional additional outputs that should also be included in the generated PSBT
|
|
482
|
+
*
|
|
483
|
+
* @private
|
|
484
|
+
*/
|
|
485
|
+
async _getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs) {
|
|
486
|
+
if (this.address == null)
|
|
487
|
+
throw new Error("Cannot create funded PSBT, because the address is not known! This can happen after a swap is recovered.");
|
|
488
|
+
let bitcoinWallet;
|
|
489
|
+
if ((0, IBitcoinWallet_1.isIBitcoinWallet)(_bitcoinWallet)) {
|
|
490
|
+
bitcoinWallet = _bitcoinWallet;
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
bitcoinWallet = new SingleAddressBitcoinWallet_1.SingleAddressBitcoinWallet(this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork, _bitcoinWallet);
|
|
494
|
+
}
|
|
495
|
+
//TODO: Maybe re-introduce fee rate check here if passed from the user
|
|
496
|
+
if (feeRate == null) {
|
|
497
|
+
feeRate = await bitcoinWallet.getFeeRate();
|
|
498
|
+
}
|
|
499
|
+
const basePsbt = new btc_signer_1.Transaction({
|
|
500
|
+
allowUnknownOutputs: true,
|
|
501
|
+
allowLegacyWitnessUtxo: true
|
|
502
|
+
});
|
|
503
|
+
basePsbt.addOutput({
|
|
504
|
+
amount: this.amount,
|
|
505
|
+
script: (0, BitcoinUtils_1.toOutputScript)(this.wrapper._options.bitcoinNetwork, this.address)
|
|
506
|
+
});
|
|
507
|
+
if (additionalOutputs != null)
|
|
508
|
+
additionalOutputs.forEach(output => {
|
|
509
|
+
basePsbt.addOutput({
|
|
510
|
+
amount: output.amount,
|
|
511
|
+
script: output.outputScript ?? (0, BitcoinUtils_1.toOutputScript)(this.wrapper._options.bitcoinNetwork, output.address)
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
const psbt = await bitcoinWallet.fundPsbt(basePsbt, feeRate);
|
|
515
|
+
//Sign every input
|
|
516
|
+
const signInputs = [];
|
|
517
|
+
for (let i = 0; i < psbt.inputsLength; i++) {
|
|
518
|
+
signInputs.push(i);
|
|
519
|
+
}
|
|
520
|
+
const serializedPsbt = buffer_1.Buffer.from(psbt.toPSBT());
|
|
521
|
+
return {
|
|
522
|
+
psbt,
|
|
523
|
+
psbtHex: serializedPsbt.toString("hex"),
|
|
524
|
+
psbtBase64: serializedPsbt.toString("base64"),
|
|
525
|
+
signInputs,
|
|
526
|
+
feeRate
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* @inheritDoc
|
|
531
|
+
*/
|
|
532
|
+
getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs) {
|
|
533
|
+
if (this._state !== FromBTCSwapState.CLAIM_COMMITED)
|
|
534
|
+
throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
|
|
535
|
+
if (this.txId != null)
|
|
536
|
+
throw new Error("Bitcoin transaction already submitted for this swap!");
|
|
537
|
+
return this._getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs);
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* @inheritDoc
|
|
541
|
+
*
|
|
542
|
+
* @throws {Error} if the swap is in invalid state (not in {@link FromBTCSwapState.CLAIM_COMMITED}), or if
|
|
543
|
+
* the swap bitcoin address already expired.
|
|
544
|
+
*/
|
|
545
|
+
async submitPsbt(_psbt) {
|
|
546
|
+
const psbt = (0, BitcoinUtils_1.parsePsbtTransaction)(_psbt);
|
|
547
|
+
if (this._state !== FromBTCSwapState.CLAIM_COMMITED)
|
|
548
|
+
throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
|
|
549
|
+
if (this.txId != null)
|
|
550
|
+
throw new Error("Bitcoin transaction already submitted for this swap!");
|
|
551
|
+
//Ensure not expired
|
|
552
|
+
if (this.getTimeoutTime() < Date.now()) {
|
|
553
|
+
throw new Error("Swap address expired!");
|
|
554
|
+
}
|
|
555
|
+
const output0 = psbt.getOutput(0);
|
|
556
|
+
if (this.amount != null && output0.amount !== this.amount)
|
|
557
|
+
throw new Error("PSBT output amount invalid, expected: " + this.amount + " got: " + output0.amount);
|
|
558
|
+
if (this.address != null) {
|
|
559
|
+
const expectedOutputScript = (0, BitcoinUtils_1.toOutputScript)(this.wrapper._options.bitcoinNetwork, this.address);
|
|
560
|
+
if (output0.script == null || !expectedOutputScript.equals(output0.script))
|
|
561
|
+
throw new Error("PSBT output script invalid!");
|
|
562
|
+
}
|
|
563
|
+
if (!psbt.isFinal)
|
|
564
|
+
psbt.finalize();
|
|
565
|
+
const txId = await this.wrapper._btcRpc.sendRawTransaction(buffer_1.Buffer.from(psbt.toBytes(true, true)).toString("hex"));
|
|
566
|
+
await this._setSubmittedBitcoinTx(txId, psbt);
|
|
567
|
+
return txId;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* @inheritDoc
|
|
571
|
+
*/
|
|
572
|
+
async estimateBitcoinFee(_bitcoinWallet, feeRate) {
|
|
573
|
+
if (this.address == null || this.amount == null)
|
|
574
|
+
return null;
|
|
575
|
+
const bitcoinWallet = (0, BitcoinWalletUtils_1.toBitcoinWallet)(_bitcoinWallet, this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork);
|
|
576
|
+
const txFee = await bitcoinWallet.getTransactionFee(this.address, this.amount, feeRate);
|
|
577
|
+
if (txFee == null)
|
|
578
|
+
return null;
|
|
579
|
+
return (0, TokenAmount_1.toTokenAmount)(BigInt(txFee), Token_1.BitcoinTokens.BTC, this.wrapper._prices);
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* @inheritDoc
|
|
583
|
+
*/
|
|
584
|
+
async sendBitcoinTransaction(wallet, feeRate) {
|
|
585
|
+
if (this.address == null || this.amount == null)
|
|
586
|
+
throw new Error("Cannot send bitcoin transaction, because the address is not known! This can happen after a swap is recovered.");
|
|
587
|
+
if (this._state !== FromBTCSwapState.CLAIM_COMMITED)
|
|
588
|
+
throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
|
|
589
|
+
if (this.txId != null)
|
|
590
|
+
throw new Error("Bitcoin transaction already submitted for this swap!");
|
|
591
|
+
//Ensure not expired
|
|
592
|
+
if (this.getTimeoutTime() < Date.now()) {
|
|
593
|
+
throw new Error("Swap address expired!");
|
|
594
|
+
}
|
|
595
|
+
if ((0, IBitcoinWallet_1.isIBitcoinWallet)(wallet)) {
|
|
596
|
+
const txId = await wallet.sendTransaction(this.address, this.amount, feeRate);
|
|
597
|
+
await this._setSubmittedBitcoinTx(txId);
|
|
598
|
+
return txId;
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
const { psbt, psbtHex, psbtBase64, signInputs } = await this.getFundedPsbt(wallet, feeRate);
|
|
602
|
+
const signedPsbt = await wallet.signPsbt({
|
|
603
|
+
psbt, psbtHex, psbtBase64
|
|
604
|
+
}, signInputs);
|
|
605
|
+
return await this.submitPsbt(signedPsbt);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
//////////////////////////////
|
|
609
|
+
//// Execution
|
|
610
|
+
/**
|
|
611
|
+
* Executes the swap with the provided bitcoin wallet,
|
|
612
|
+
*
|
|
613
|
+
* @param dstSigner Signer on the destination network, needs to have the same address as the one specified when
|
|
614
|
+
* quote was created, this is required for legacy swaps because the destination wallet needs to actively open
|
|
615
|
+
* a bitcoin swap address to which the BTC is then sent, this means that the address also needs to have enough
|
|
616
|
+
* native tokens to pay for gas on the destination network
|
|
617
|
+
* @param wallet Bitcoin wallet to use to sign the bitcoin transaction, can also be null - then the execution waits
|
|
618
|
+
* till a transaction is received from an external wallet
|
|
619
|
+
* @param callbacks Callbacks to track the progress of the swap
|
|
620
|
+
* @param options Optional options for the swap like feeRate, AbortSignal, and timeouts/intervals
|
|
621
|
+
*
|
|
622
|
+
* @returns {boolean} Whether a swap was settled automatically by swap watchtowers or requires manual claim by the
|
|
623
|
+
* user, in case `false` is returned the user should call `swap.claim()` to settle the swap on the destination manually
|
|
624
|
+
*/
|
|
625
|
+
async execute(dstSigner, wallet, callbacks, options) {
|
|
626
|
+
if (this._state === FromBTCSwapState.FAILED)
|
|
627
|
+
throw new Error("Swap failed!");
|
|
628
|
+
if (this._state === FromBTCSwapState.EXPIRED)
|
|
629
|
+
throw new Error("Swap address expired!");
|
|
630
|
+
if (this._state === FromBTCSwapState.QUOTE_EXPIRED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED)
|
|
631
|
+
throw new Error("Swap quote expired!");
|
|
632
|
+
if (this._state === FromBTCSwapState.CLAIM_CLAIMED)
|
|
633
|
+
throw new Error("Swap already settled!");
|
|
634
|
+
if (this._state === FromBTCSwapState.PR_CREATED) {
|
|
635
|
+
await this.commit(dstSigner, options?.abortSignal, undefined, callbacks?.onDestinationCommitSent);
|
|
636
|
+
}
|
|
637
|
+
if (this._state === FromBTCSwapState.CLAIM_COMMITED) {
|
|
638
|
+
if (wallet != null) {
|
|
639
|
+
const bitcoinPaymentSent = await this.getBitcoinPayment();
|
|
640
|
+
if (bitcoinPaymentSent == null && this.txId == null) {
|
|
641
|
+
//Send btc tx
|
|
642
|
+
const txId = await this.sendBitcoinTransaction(wallet, options?.feeRate);
|
|
643
|
+
if (callbacks?.onSourceTransactionSent != null)
|
|
644
|
+
callbacks.onSourceTransactionSent(txId);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
const txId = await this.waitForBitcoinTransaction(callbacks?.onSourceTransactionConfirmationStatus, options?.btcTxCheckIntervalSeconds, options?.abortSignal);
|
|
648
|
+
if (callbacks?.onSourceTransactionConfirmed != null)
|
|
649
|
+
callbacks.onSourceTransactionConfirmed(txId);
|
|
650
|
+
}
|
|
651
|
+
// @ts-ignore
|
|
652
|
+
if (this._state === FromBTCSwapState.CLAIM_CLAIMED)
|
|
653
|
+
return true;
|
|
654
|
+
if (this._state === FromBTCSwapState.BTC_TX_CONFIRMED) {
|
|
655
|
+
const success = await this.waitTillClaimed(options?.maxWaitTillAutomaticSettlementSeconds ?? 60, options?.abortSignal);
|
|
656
|
+
if (success && callbacks?.onSwapSettled != null)
|
|
657
|
+
callbacks.onSwapSettled(this.getOutputTxId());
|
|
658
|
+
return success;
|
|
659
|
+
}
|
|
660
|
+
throw new Error("Invalid state reached!");
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* @internal
|
|
664
|
+
*/
|
|
665
|
+
async _getExecutionStatus(options) {
|
|
666
|
+
const state = this._state;
|
|
667
|
+
const now = Date.now();
|
|
668
|
+
const timeoutTime = this.getTimeoutTime();
|
|
669
|
+
let confirmations;
|
|
670
|
+
let bitcoinTxId;
|
|
671
|
+
let destinationSetupStatus = "awaiting";
|
|
672
|
+
let bitcoinPaymentStatus = "inactive";
|
|
673
|
+
let destinationSettlementStatus = "inactive";
|
|
674
|
+
let buildCurrentAction = async () => undefined;
|
|
675
|
+
switch (state) {
|
|
676
|
+
case FromBTCSwapState.PR_CREATED: {
|
|
677
|
+
const quoteValid = await this._verifyQuoteValid();
|
|
678
|
+
destinationSetupStatus = quoteValid && timeoutTime >= now ? "awaiting" : "soft_expired";
|
|
679
|
+
if (quoteValid && timeoutTime >= now) {
|
|
680
|
+
buildCurrentAction = this._buildInitSmartChainTxAction.bind(this);
|
|
681
|
+
}
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
case FromBTCSwapState.QUOTE_SOFT_EXPIRED:
|
|
685
|
+
destinationSetupStatus = "soft_expired";
|
|
686
|
+
break;
|
|
687
|
+
case FromBTCSwapState.QUOTE_EXPIRED:
|
|
688
|
+
destinationSetupStatus = "expired";
|
|
689
|
+
break;
|
|
690
|
+
case FromBTCSwapState.CLAIM_COMMITED:
|
|
691
|
+
case FromBTCSwapState.EXPIRED:
|
|
692
|
+
case FromBTCSwapState.FAILED:
|
|
693
|
+
const bitcoinPayment = this.address == null ? null : await this.getBitcoinPayment();
|
|
694
|
+
bitcoinTxId = bitcoinPayment?.txId;
|
|
695
|
+
let bitcoinConfirmationDelay;
|
|
696
|
+
if (bitcoinPayment != null && bitcoinPayment.confirmations < bitcoinPayment.targetConfirmations) {
|
|
697
|
+
const tx = await this.wrapper._btcRpc.getTransaction(bitcoinPayment.txId);
|
|
698
|
+
const result = tx == null
|
|
699
|
+
? null
|
|
700
|
+
: await this.wrapper._btcRpc.getConfirmationDelay(tx, bitcoinPayment.targetConfirmations);
|
|
701
|
+
bitcoinConfirmationDelay = result ?? -1;
|
|
702
|
+
}
|
|
703
|
+
destinationSetupStatus = "completed";
|
|
704
|
+
if (bitcoinPayment == null) {
|
|
705
|
+
if (this.txId != null) {
|
|
706
|
+
bitcoinPaymentStatus = state === FromBTCSwapState.FAILED ? "expired" : "received";
|
|
707
|
+
if (state !== FromBTCSwapState.FAILED) {
|
|
708
|
+
buildCurrentAction = this._buildWaitBitcoinConfirmationsAction.bind(this, -1, "Wait for bitcoin transaction to be picked up by the RPC and confirmed.");
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
bitcoinPaymentStatus = "awaiting";
|
|
713
|
+
if (state === FromBTCSwapState.EXPIRED)
|
|
714
|
+
bitcoinPaymentStatus = "soft_expired";
|
|
715
|
+
if (state === FromBTCSwapState.FAILED)
|
|
716
|
+
bitcoinPaymentStatus = "expired";
|
|
717
|
+
if (state === FromBTCSwapState.CLAIM_COMMITED && timeoutTime >= now &&
|
|
718
|
+
this.address != null && this.amount != null) {
|
|
719
|
+
buildCurrentAction = this._buildSendToAddressOrSignPsbtAction.bind(this);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
else if (bitcoinPayment.confirmations >= bitcoinPayment.targetConfirmations) {
|
|
724
|
+
bitcoinPaymentStatus = "confirmed";
|
|
725
|
+
if (state !== FromBTCSwapState.FAILED) {
|
|
726
|
+
buildCurrentAction = this._buildWaitBitcoinConfirmationsAction.bind(this, bitcoinConfirmationDelay ?? -1, undefined);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
bitcoinPaymentStatus = "received";
|
|
731
|
+
confirmations = {
|
|
732
|
+
current: bitcoinPayment.confirmations,
|
|
733
|
+
target: bitcoinPayment.targetConfirmations,
|
|
734
|
+
etaSeconds: bitcoinConfirmationDelay ?? -1
|
|
735
|
+
};
|
|
736
|
+
if (state !== FromBTCSwapState.FAILED) {
|
|
737
|
+
buildCurrentAction = this._buildWaitBitcoinConfirmationsAction.bind(this, bitcoinConfirmationDelay ?? -1, undefined);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
destinationSettlementStatus = state === FromBTCSwapState.FAILED ? "expired" : "inactive";
|
|
741
|
+
break;
|
|
742
|
+
case FromBTCSwapState.BTC_TX_CONFIRMED:
|
|
743
|
+
destinationSetupStatus = "completed";
|
|
744
|
+
bitcoinPaymentStatus = "confirmed";
|
|
745
|
+
if (this.btcTxConfirmedAt == null ||
|
|
746
|
+
options?.maxWaitTillAutomaticSettlementSeconds === 0 ||
|
|
747
|
+
(now - this.btcTxConfirmedAt) > (options?.maxWaitTillAutomaticSettlementSeconds ?? 60) * 1000) {
|
|
748
|
+
destinationSettlementStatus = "awaiting_manual";
|
|
749
|
+
buildCurrentAction = this._buildClaimSmartChainTxAction.bind(this);
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
destinationSettlementStatus = "awaiting_automatic";
|
|
753
|
+
buildCurrentAction = this._buildWaitSettlementAction.bind(this, options?.maxWaitTillAutomaticSettlementSeconds);
|
|
754
|
+
}
|
|
755
|
+
break;
|
|
756
|
+
case FromBTCSwapState.CLAIM_CLAIMED:
|
|
757
|
+
destinationSetupStatus = "completed";
|
|
758
|
+
bitcoinPaymentStatus = "confirmed";
|
|
759
|
+
destinationSettlementStatus = "settled";
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
if (bitcoinPaymentStatus === "confirmed") {
|
|
763
|
+
const requiredConfirmations = this.getRequiredConfirmationsCount();
|
|
764
|
+
if (!Number.isNaN(requiredConfirmations)) {
|
|
765
|
+
confirmations = {
|
|
766
|
+
current: requiredConfirmations,
|
|
767
|
+
target: requiredConfirmations,
|
|
768
|
+
etaSeconds: 0
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return {
|
|
773
|
+
steps: [
|
|
774
|
+
{
|
|
775
|
+
type: "Setup",
|
|
776
|
+
side: "destination",
|
|
777
|
+
chain: this.chainIdentifier,
|
|
778
|
+
title: "Open Bitcoin swap address",
|
|
779
|
+
description: `Create the escrow on the ${this.chainIdentifier} side to open the Bitcoin swap address`,
|
|
780
|
+
status: destinationSetupStatus,
|
|
781
|
+
setupTxId: this._commitTxId
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
type: "Payment",
|
|
785
|
+
side: "source",
|
|
786
|
+
chain: "BITCOIN",
|
|
787
|
+
title: "Bitcoin payment",
|
|
788
|
+
description: "Send Bitcoin to the swap address and wait for the transaction to confirm",
|
|
789
|
+
status: bitcoinPaymentStatus,
|
|
790
|
+
confirmations,
|
|
791
|
+
initTxId: this.txId ?? bitcoinTxId,
|
|
792
|
+
settleTxId: this.txId
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
type: "Settlement",
|
|
796
|
+
side: "destination",
|
|
797
|
+
chain: this.chainIdentifier,
|
|
798
|
+
title: "Destination settlement",
|
|
799
|
+
description: `Wait for automatic settlement on the ${this.chainIdentifier} side, or settle manually if it takes too long`,
|
|
800
|
+
status: destinationSettlementStatus,
|
|
801
|
+
initTxId: this._commitTxId,
|
|
802
|
+
settleTxId: this._claimTxId
|
|
803
|
+
}
|
|
804
|
+
],
|
|
805
|
+
buildCurrentAction,
|
|
806
|
+
state
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* @inheritDoc
|
|
811
|
+
* @internal
|
|
812
|
+
*/
|
|
813
|
+
async _submitExecutionTransactions(txs, abortSignal, requiredStates, idempotent) {
|
|
814
|
+
if (txs.length === 0)
|
|
815
|
+
throw new Error("Need to submit at least 1 transaction in the array, submitted empty array of transactions!");
|
|
816
|
+
if (idempotent) {
|
|
817
|
+
// Handle idempotent calls
|
|
818
|
+
let idempotencyTriggered = false;
|
|
819
|
+
const txIds = [];
|
|
820
|
+
for (let tx of txs) {
|
|
821
|
+
let parsedTx;
|
|
822
|
+
if (typeof (tx) === "string") {
|
|
823
|
+
try {
|
|
824
|
+
parsedTx = await this.wrapper._chain.deserializeSignedTx(tx);
|
|
825
|
+
}
|
|
826
|
+
catch (e) { }
|
|
827
|
+
try {
|
|
828
|
+
parsedTx = (0, BitcoinUtils_1.parsePsbtTransaction)(tx);
|
|
829
|
+
}
|
|
830
|
+
catch (e) { }
|
|
831
|
+
}
|
|
832
|
+
else {
|
|
833
|
+
parsedTx = tx;
|
|
834
|
+
}
|
|
835
|
+
if (parsedTx == null) {
|
|
836
|
+
this.logger.debug("_submitExecutionTransactions(): Failed to parse provided execution transaction: ", tx);
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
if (parsedTx instanceof btc_signer_1.Transaction) {
|
|
840
|
+
// Bitcoin tx
|
|
841
|
+
const btcTx = await this.wrapper._btcRpc.parseTransaction(buffer_1.Buffer.from(parsedTx.toBytes(true)).toString("hex"));
|
|
842
|
+
if (btcTx.txid === this.txId)
|
|
843
|
+
idempotencyTriggered = true;
|
|
844
|
+
txIds.push(btcTx.txid);
|
|
845
|
+
}
|
|
846
|
+
else {
|
|
847
|
+
// SC tx
|
|
848
|
+
if (this.wrapper._chain.getTxId != null) {
|
|
849
|
+
const txId = await this.wrapper._chain.getTxId(parsedTx);
|
|
850
|
+
if (this._commitTxId === txId || this._claimTxId === txId)
|
|
851
|
+
idempotencyTriggered = true;
|
|
852
|
+
txIds.push(txId);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (idempotencyTriggered)
|
|
857
|
+
return txIds;
|
|
858
|
+
}
|
|
859
|
+
if (requiredStates != null && !requiredStates.includes(this._state))
|
|
860
|
+
throw new Error("Swap state has changed before transactions were submitted!");
|
|
861
|
+
if (this._state === FromBTCSwapState.CLAIM_COMMITED) {
|
|
862
|
+
let psbt;
|
|
863
|
+
if (txs.length !== 1)
|
|
864
|
+
throw new Error("Need to submit exactly 1 signed PSBT!");
|
|
865
|
+
if (typeof (txs[0]) !== "string" && !(txs[0] instanceof btc_signer_1.Transaction))
|
|
866
|
+
throw new Error("Must submit a valid PSBT as hex/base64 string or `@scure/btc-signer` Transaction object!");
|
|
867
|
+
psbt = txs[0];
|
|
868
|
+
return [await this.submitPsbt(psbt)];
|
|
869
|
+
}
|
|
870
|
+
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
871
|
+
if (!await this._verifyQuoteValid())
|
|
872
|
+
throw new Error("Quote is already expired!");
|
|
873
|
+
if (this.getTimeoutTime() < Date.now())
|
|
874
|
+
throw new Error("Swap address already expired or close to expiry!");
|
|
875
|
+
const parsedTxs = [];
|
|
876
|
+
for (let tx of txs) {
|
|
877
|
+
parsedTxs.push(typeof (tx) === "string" ? await this.wrapper._chain.deserializeSignedTx(tx) : tx);
|
|
878
|
+
}
|
|
879
|
+
const txIds = await this.wrapper._chain.sendSignedAndConfirm(parsedTxs, true, abortSignal, false);
|
|
880
|
+
await this.waitTillCommited(abortSignal);
|
|
881
|
+
return txIds;
|
|
882
|
+
}
|
|
883
|
+
if (this._state === FromBTCSwapState.BTC_TX_CONFIRMED) {
|
|
884
|
+
const parsedTxs = [];
|
|
885
|
+
for (let tx of txs) {
|
|
886
|
+
parsedTxs.push(typeof (tx) === "string" ? await this.wrapper._chain.deserializeSignedTx(tx) : tx);
|
|
887
|
+
}
|
|
888
|
+
const txIds = await this.wrapper._chain.sendSignedAndConfirm(parsedTxs, true, abortSignal, false);
|
|
889
|
+
await this.waitTillClaimed(undefined, abortSignal);
|
|
890
|
+
return txIds;
|
|
891
|
+
}
|
|
892
|
+
throw new Error("Invalid swap state for transaction submission!");
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* @internal
|
|
896
|
+
*/
|
|
897
|
+
async _buildSendToAddressOrSignPsbtAction(actionOptions) {
|
|
898
|
+
if (this.address == null)
|
|
899
|
+
throw new Error("Bitcoin swap address not known!");
|
|
900
|
+
if (this.amount == null)
|
|
901
|
+
throw new Error("Bitcoin swap amount not known!");
|
|
902
|
+
if (actionOptions?.bitcoinWallet == null) {
|
|
903
|
+
return {
|
|
904
|
+
type: "SendToAddress",
|
|
905
|
+
name: "Deposit on Bitcoin",
|
|
906
|
+
description: "Send funds to the bitcoin swap address",
|
|
907
|
+
chain: "BITCOIN",
|
|
908
|
+
txs: [{
|
|
909
|
+
type: "BITCOIN_ADDRESS",
|
|
910
|
+
address: this.address,
|
|
911
|
+
hyperlink: this._getHyperlink(),
|
|
912
|
+
amount: (0, TokenAmount_1.toTokenAmount)(this.amount, Token_1.BitcoinTokens.BTC, this.wrapper._prices)
|
|
913
|
+
}],
|
|
914
|
+
waitForTransactions: async (maxWaitTimeSeconds, pollIntervalSeconds, abortSignal) => {
|
|
915
|
+
let btcTxId;
|
|
916
|
+
const abortController = (0, Utils_1.extendAbortController)(abortSignal, maxWaitTimeSeconds, "Timed out waiting for bitcoin transaction");
|
|
917
|
+
try {
|
|
918
|
+
return await this.waitForBitcoinTransaction((txId) => {
|
|
919
|
+
btcTxId = txId;
|
|
920
|
+
abortController.abort();
|
|
921
|
+
}, pollIntervalSeconds, abortController.signal);
|
|
922
|
+
}
|
|
923
|
+
catch (e) {
|
|
924
|
+
if (btcTxId != null)
|
|
925
|
+
return btcTxId;
|
|
926
|
+
throw e;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
return {
|
|
932
|
+
type: "SignPSBT",
|
|
933
|
+
name: "Deposit on Bitcoin",
|
|
934
|
+
description: "Send funds to the bitcoin swap address",
|
|
935
|
+
chain: "BITCOIN",
|
|
936
|
+
txs: [{
|
|
937
|
+
...await this.getFundedPsbt(actionOptions.bitcoinWallet, actionOptions?.bitcoinFeeRate),
|
|
938
|
+
type: "FUNDED_PSBT"
|
|
939
|
+
}],
|
|
940
|
+
submitPsbt: async (signedPsbt, idempotent) => {
|
|
941
|
+
return this._submitExecutionTransactions(Array.isArray(signedPsbt) ? signedPsbt : [signedPsbt], undefined, [FromBTCSwapState.CLAIM_COMMITED], idempotent);
|
|
942
|
+
}
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* @internal
|
|
947
|
+
*/
|
|
948
|
+
async _buildWaitBitcoinConfirmationsAction(confirmationDelay, description) {
|
|
949
|
+
return {
|
|
950
|
+
type: "Wait",
|
|
951
|
+
name: "Bitcoin confirmations",
|
|
952
|
+
description: description ?? "Wait for bitcoin transaction to confirm",
|
|
953
|
+
pollTimeSeconds: 10,
|
|
954
|
+
expectedTimeSeconds: confirmationDelay === -1 ? -1 : Math.floor(confirmationDelay / 1000),
|
|
955
|
+
wait: async (maxWaitTimeSeconds, pollIntervalSeconds, abortSignal, btcConfirmationsCallback) => {
|
|
956
|
+
const abortController = (0, Utils_1.extendAbortController)(abortSignal, maxWaitTimeSeconds, "Timed out waiting for bitcoin transaction to confirm");
|
|
957
|
+
await this.waitForBitcoinTransaction(btcConfirmationsCallback, pollIntervalSeconds, abortController.signal);
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* @internal
|
|
963
|
+
*/
|
|
964
|
+
async _buildWaitSettlementAction(maxWaitTillAutomaticSettlementSeconds) {
|
|
965
|
+
return {
|
|
966
|
+
type: "Wait",
|
|
967
|
+
name: "Automatic settlement",
|
|
968
|
+
description: "Wait for automatic settlement by the watchtower",
|
|
969
|
+
pollTimeSeconds: 5,
|
|
970
|
+
expectedTimeSeconds: 10,
|
|
971
|
+
wait: async (maxWaitTimeSeconds, pollIntervalSeconds, abortSignal) => {
|
|
972
|
+
await this.waitTillClaimed(maxWaitTimeSeconds ?? maxWaitTillAutomaticSettlementSeconds ?? 60, abortSignal, pollIntervalSeconds);
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* @internal
|
|
978
|
+
*/
|
|
979
|
+
async _buildInitSmartChainTxAction(actionOptions) {
|
|
980
|
+
return {
|
|
981
|
+
type: "SignSmartChainTransaction",
|
|
982
|
+
name: "Initiate swap",
|
|
983
|
+
description: `Opens up the bitcoin swap address on the ${this.chainIdentifier} side`,
|
|
984
|
+
chain: this.chainIdentifier,
|
|
985
|
+
txs: await this.prepareTransactions(this.txsCommit(actionOptions?.skipChecks)),
|
|
986
|
+
submitTransactions: async (txs, abortSignal, idempotent) => {
|
|
987
|
+
return this._submitExecutionTransactions(txs, abortSignal, [FromBTCSwapState.PR_CREATED, FromBTCSwapState.QUOTE_SOFT_EXPIRED], idempotent);
|
|
988
|
+
},
|
|
989
|
+
requiredSigner: this._getInitiator()
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* @inheritDoc
|
|
994
|
+
* @internal
|
|
995
|
+
*/
|
|
996
|
+
async _buildClaimSmartChainTxAction(actionOptions) {
|
|
997
|
+
const signerAddress = await this.wrapper._getSignerAddress(actionOptions?.manualSettlementSmartChainSigner);
|
|
998
|
+
return {
|
|
999
|
+
type: "SignSmartChainTransaction",
|
|
1000
|
+
name: "Settle manually",
|
|
1001
|
+
description: "Manually settle (claim) the swap on the destination smart chain",
|
|
1002
|
+
chain: this.chainIdentifier,
|
|
1003
|
+
txs: await this.prepareTransactions(this.txsClaim(actionOptions?.manualSettlementSmartChainSigner)),
|
|
1004
|
+
submitTransactions: async (txs, abortSignal, idempotent) => {
|
|
1005
|
+
return this._submitExecutionTransactions(txs, abortSignal, [FromBTCSwapState.BTC_TX_CONFIRMED], idempotent);
|
|
1006
|
+
},
|
|
1007
|
+
requiredSigner: signerAddress ?? this._getInitiator()
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* @inheritDoc
|
|
1012
|
+
*
|
|
1013
|
+
* @param options.bitcoinFeeRate Optional fee rate to use for the created Bitcoin transaction
|
|
1014
|
+
* @param options.bitcoinWallet Bitcoin wallet to use, when provided the function returns a funded
|
|
1015
|
+
* psbt (`"FUNDED_PSBT"`), if not passed just a bitcoin receive address is returned (`"ADDRESS"`)
|
|
1016
|
+
* @param options.skipChecks Skip checks like making sure init signature is still valid and swap
|
|
1017
|
+
* wasn't commited yet (this is handled on swap creation, if you commit right after quoting, you
|
|
1018
|
+
* can use `skipChecks=true`)
|
|
1019
|
+
* @param options.manualSettlementSmartChainSigner Optional smart chain signer to create a manual claim (settlement) transaction
|
|
1020
|
+
* @param options.maxWaitTillAutomaticSettlementSeconds Maximum time to wait for an automatic settlement after
|
|
1021
|
+
* the bitcoin transaction is confirmed (defaults to 60 seconds)
|
|
1022
|
+
*/
|
|
1023
|
+
async getExecutionAction(options) {
|
|
1024
|
+
const executionStatus = await this._getExecutionStatus(options);
|
|
1025
|
+
return executionStatus.buildCurrentAction(options);
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* @inheritDoc
|
|
1029
|
+
*/
|
|
1030
|
+
async getExecutionStatus(options) {
|
|
1031
|
+
const executionStatus = await this._getExecutionStatus(options);
|
|
1032
|
+
return {
|
|
1033
|
+
steps: executionStatus.steps,
|
|
1034
|
+
currentAction: options?.skipBuildingAction ? undefined : await executionStatus.buildCurrentAction(options),
|
|
1035
|
+
stateInfo: this._getStateInfo(executionStatus.state)
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* @inheritDoc
|
|
1040
|
+
*/
|
|
1041
|
+
async getExecutionSteps(options) {
|
|
1042
|
+
return (await this._getExecutionStatus(options)).steps;
|
|
1043
|
+
}
|
|
1044
|
+
//////////////////////////////
|
|
1045
|
+
//// Commit
|
|
1046
|
+
/**
|
|
1047
|
+
* @inheritDoc
|
|
1048
|
+
*
|
|
1049
|
+
* @throws {Error} If invalid signer is provided that doesn't match the swap data
|
|
1050
|
+
*/
|
|
1051
|
+
async commit(_signer, abortSignal, skipChecks, onBeforeTxSent) {
|
|
1052
|
+
const signer = (0, base_1.isAbstractSigner)(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
|
|
1053
|
+
this.checkSigner(signer);
|
|
1054
|
+
let txCount = 0;
|
|
1055
|
+
const txs = await this.txsCommit(skipChecks);
|
|
1056
|
+
const result = await this.wrapper._chain.sendAndConfirm(signer, txs, true, abortSignal, undefined, (txId) => {
|
|
1057
|
+
txCount++;
|
|
1058
|
+
if (onBeforeTxSent != null && txCount === txs.length)
|
|
1059
|
+
onBeforeTxSent(txId);
|
|
1060
|
+
return Promise.resolve();
|
|
1061
|
+
});
|
|
1062
|
+
this._commitTxId = result[result.length - 1];
|
|
1063
|
+
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED || this._state === FromBTCSwapState.QUOTE_EXPIRED) {
|
|
1064
|
+
await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
|
|
1065
|
+
}
|
|
1066
|
+
return this._commitTxId;
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* @inheritDoc
|
|
1070
|
+
*/
|
|
1071
|
+
async waitTillCommited(abortSignal) {
|
|
1072
|
+
if (this._state === FromBTCSwapState.CLAIM_COMMITED || this._state === FromBTCSwapState.CLAIM_CLAIMED)
|
|
1073
|
+
return Promise.resolve();
|
|
1074
|
+
if (this._state !== FromBTCSwapState.PR_CREATED && this._state !== FromBTCSwapState.QUOTE_SOFT_EXPIRED)
|
|
1075
|
+
throw new Error("Invalid state");
|
|
1076
|
+
const abortController = (0, Utils_1.extendAbortController)(abortSignal);
|
|
1077
|
+
const result = await Promise.race([
|
|
1078
|
+
this.watchdogWaitTillCommited(undefined, abortController.signal),
|
|
1079
|
+
this.waitTillState(FromBTCSwapState.CLAIM_COMMITED, "gte", abortController.signal).then(() => 0)
|
|
1080
|
+
]);
|
|
1081
|
+
abortController.abort();
|
|
1082
|
+
if (result === 0) {
|
|
1083
|
+
this.logger.debug("waitTillCommited(): Resolved from state changed");
|
|
1084
|
+
}
|
|
1085
|
+
else if (result != null) {
|
|
1086
|
+
this.logger.debug("waitTillCommited(): Resolved from watchdog - commited");
|
|
1087
|
+
}
|
|
1088
|
+
if (result === null) {
|
|
1089
|
+
this.logger.debug("waitTillCommited(): Resolved from watchdog - signature expired");
|
|
1090
|
+
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1091
|
+
await this._saveAndEmit(FromBTCSwapState.QUOTE_EXPIRED);
|
|
1092
|
+
}
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1096
|
+
if (typeof (result) === "object" && result.getInitTxId != null && this._commitTxId == null)
|
|
1097
|
+
this._commitTxId = await result.getInitTxId();
|
|
1098
|
+
await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
//////////////////////////////
|
|
1102
|
+
//// Claim
|
|
1103
|
+
/**
|
|
1104
|
+
* Returns transactions for settling (claiming) the swap if the swap requires manual settlement, you can check so
|
|
1105
|
+
* with isClaimable. After sending the transaction manually be sure to call the waitTillClaimed function to wait
|
|
1106
|
+
* till the claim transaction is observed, processed by the SDK and state of the swap properly updated.
|
|
1107
|
+
*
|
|
1108
|
+
* @remarks
|
|
1109
|
+
* Might also return transactions necessary to sync the bitcoin light client.
|
|
1110
|
+
*
|
|
1111
|
+
* @param _signer Address of the signer to create the claim transactions for
|
|
1112
|
+
*
|
|
1113
|
+
* @throws {Error} If the swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
|
|
1114
|
+
*/
|
|
1115
|
+
async txsClaim(_signer) {
|
|
1116
|
+
let signer = undefined;
|
|
1117
|
+
if (_signer != null) {
|
|
1118
|
+
if (typeof (_signer) === "string") {
|
|
1119
|
+
signer = _signer;
|
|
1120
|
+
}
|
|
1121
|
+
else if ((0, base_1.isAbstractSigner)(_signer)) {
|
|
1122
|
+
signer = _signer;
|
|
1123
|
+
}
|
|
1124
|
+
else {
|
|
1125
|
+
signer = await this.wrapper._chain.wrapSigner(_signer);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if (this._state !== FromBTCSwapState.BTC_TX_CONFIRMED)
|
|
1129
|
+
throw new Error("Must be in BTC_TX_CONFIRMED state!");
|
|
1130
|
+
if (this.txId == null || this.vout == null)
|
|
1131
|
+
throw new Error("Bitcoin transaction ID not known!");
|
|
1132
|
+
const tx = await this.wrapper._btcRpc.getTransaction(this.txId);
|
|
1133
|
+
if (tx == null)
|
|
1134
|
+
throw new Error("Bitcoin transaction not found on the network!");
|
|
1135
|
+
this.requiredConfirmations ??= this.inferRequiredConfirmationsCount(tx, this.vout);
|
|
1136
|
+
if (this.requiredConfirmations == null)
|
|
1137
|
+
throw new Error("Cannot create claim transaction, because required confirmations are not known and cannot be infered! This can happen after a swap is recovered.");
|
|
1138
|
+
if (tx.blockhash == null || tx.confirmations == null || tx.blockheight == null || tx.confirmations < this.requiredConfirmations)
|
|
1139
|
+
throw new Error("Bitcoin transaction not confirmed yet!");
|
|
1140
|
+
return await this._contract.txsClaimWithTxData(signer ?? this._getInitiator(), this._data, {
|
|
1141
|
+
blockhash: tx.blockhash,
|
|
1142
|
+
confirmations: tx.confirmations,
|
|
1143
|
+
txid: tx.txid,
|
|
1144
|
+
hex: tx.hex,
|
|
1145
|
+
height: tx.blockheight
|
|
1146
|
+
}, this.requiredConfirmations, this.vout, undefined, this.wrapper._synchronizer(this._contractVersion), true);
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Settles the swap by claiming the funds on the destination chain if the swap requires manual settlement, you can
|
|
1150
|
+
* check so with isClaimable.
|
|
1151
|
+
*
|
|
1152
|
+
* @remarks
|
|
1153
|
+
* Might also sync the bitcoin light client during the process.
|
|
1154
|
+
*
|
|
1155
|
+
* @param _signer Signer to use for signing the settlement transactions, can also be different to the recipient
|
|
1156
|
+
* @param abortSignal Abort signal
|
|
1157
|
+
* @param onBeforeTxSent Optional callback triggered before the claim transaction is broadcasted
|
|
1158
|
+
*
|
|
1159
|
+
* @returns Transaction ID of the settlement (claim) transaction on the destination smart chain
|
|
1160
|
+
*/
|
|
1161
|
+
async claim(_signer, abortSignal, onBeforeTxSent) {
|
|
1162
|
+
const signer = (0, base_1.isAbstractSigner)(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
|
|
1163
|
+
let txIds;
|
|
1164
|
+
try {
|
|
1165
|
+
let txCount = 0;
|
|
1166
|
+
const txs = await this.txsClaim(signer);
|
|
1167
|
+
txIds = await this.wrapper._chain.sendAndConfirm(signer, txs, true, abortSignal, undefined, (txId) => {
|
|
1168
|
+
txCount++;
|
|
1169
|
+
if (onBeforeTxSent != null && txCount === txs.length)
|
|
1170
|
+
onBeforeTxSent(txId);
|
|
1171
|
+
return Promise.resolve();
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
catch (e) {
|
|
1175
|
+
this.logger.info("claim(): Failed to claim ourselves, checking swap claim state...");
|
|
1176
|
+
if (this._state === FromBTCSwapState.CLAIM_CLAIMED) {
|
|
1177
|
+
this.logger.info("claim(): Transaction state is CLAIM_CLAIMED, swap was successfully claimed by the watchtower");
|
|
1178
|
+
return this._claimTxId;
|
|
1179
|
+
}
|
|
1180
|
+
const status = await this._contract.getCommitStatus(this._getInitiator(), this._data);
|
|
1181
|
+
if (status?.type === base_1.SwapCommitStateType.PAID) {
|
|
1182
|
+
this.logger.info("claim(): Transaction commit status is PAID, swap was successfully claimed by the watchtower");
|
|
1183
|
+
if (this._claimTxId == null)
|
|
1184
|
+
this._claimTxId = await status.getClaimTxId();
|
|
1185
|
+
const txId = buffer_1.Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
|
|
1186
|
+
await this._setBitcoinTxId(txId);
|
|
1187
|
+
await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
|
|
1188
|
+
return this._claimTxId;
|
|
1189
|
+
}
|
|
1190
|
+
throw e;
|
|
1191
|
+
}
|
|
1192
|
+
this._claimTxId = txIds[txIds.length - 1];
|
|
1193
|
+
if (this._state === FromBTCSwapState.CLAIM_COMMITED || this._state === FromBTCSwapState.BTC_TX_CONFIRMED ||
|
|
1194
|
+
this._state === FromBTCSwapState.EXPIRED || this._state === FromBTCSwapState.FAILED) {
|
|
1195
|
+
await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
|
|
1196
|
+
}
|
|
1197
|
+
return txIds[txIds.length - 1];
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* @inheritDoc
|
|
1201
|
+
*
|
|
1202
|
+
* @throws {Error} If swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
|
|
1203
|
+
* @throws {Error} If the LP refunded sooner than we were able to claim
|
|
1204
|
+
*/
|
|
1205
|
+
async waitTillClaimed(maxWaitTimeSeconds, abortSignal, pollIntervalSeconds) {
|
|
1206
|
+
if (this._state === FromBTCSwapState.CLAIM_CLAIMED)
|
|
1207
|
+
return Promise.resolve(true);
|
|
1208
|
+
if (this._state !== FromBTCSwapState.BTC_TX_CONFIRMED)
|
|
1209
|
+
throw new Error("Invalid state (not BTC_TX_CONFIRMED)");
|
|
1210
|
+
const abortController = (0, Utils_1.extendAbortController)(abortSignal);
|
|
1211
|
+
let timedOut = false;
|
|
1212
|
+
if (maxWaitTimeSeconds != null) {
|
|
1213
|
+
const timeout = setTimeout(() => {
|
|
1214
|
+
timedOut = true;
|
|
1215
|
+
abortController.abort();
|
|
1216
|
+
}, maxWaitTimeSeconds * 1000);
|
|
1217
|
+
abortController.signal.addEventListener("abort", () => clearTimeout(timeout));
|
|
1218
|
+
}
|
|
1219
|
+
let res;
|
|
1220
|
+
try {
|
|
1221
|
+
res = await Promise.race([
|
|
1222
|
+
this.watchdogWaitTillResult(pollIntervalSeconds, abortController.signal),
|
|
1223
|
+
this.waitTillState(FromBTCSwapState.CLAIM_CLAIMED, "eq", abortController.signal).then(() => 0),
|
|
1224
|
+
this.waitTillState(FromBTCSwapState.FAILED, "eq", abortController.signal).then(() => 1),
|
|
1225
|
+
]);
|
|
1226
|
+
abortController.abort();
|
|
1227
|
+
}
|
|
1228
|
+
catch (e) {
|
|
1229
|
+
abortController.abort();
|
|
1230
|
+
if (timedOut)
|
|
1231
|
+
return false;
|
|
1232
|
+
throw e;
|
|
1233
|
+
}
|
|
1234
|
+
if (res === 0) {
|
|
1235
|
+
this.logger.debug("waitTillClaimed(): Resolved from state change (CLAIM_CLAIMED)");
|
|
1236
|
+
return true;
|
|
1237
|
+
}
|
|
1238
|
+
if (res === 1) {
|
|
1239
|
+
this.logger.debug("waitTillClaimed(): Resolved from state change (FAILED)");
|
|
1240
|
+
throw new Error("Offerer refunded during claiming");
|
|
1241
|
+
}
|
|
1242
|
+
this.logger.debug("waitTillClaimed(): Resolved from watchdog");
|
|
1243
|
+
if (res?.type === base_1.SwapCommitStateType.PAID) {
|
|
1244
|
+
if (this._state !== FromBTCSwapState.CLAIM_CLAIMED) {
|
|
1245
|
+
if (this._claimTxId == null)
|
|
1246
|
+
this._claimTxId = await res.getClaimTxId();
|
|
1247
|
+
const txId = buffer_1.Buffer.from(await res.getClaimResult(), "hex").reverse().toString("hex");
|
|
1248
|
+
await this._setBitcoinTxId(txId);
|
|
1249
|
+
await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
if (res?.type === base_1.SwapCommitStateType.NOT_COMMITED || res?.type === base_1.SwapCommitStateType.EXPIRED) {
|
|
1253
|
+
if (this._state !== FromBTCSwapState.CLAIM_CLAIMED &&
|
|
1254
|
+
this._state !== FromBTCSwapState.FAILED) {
|
|
1255
|
+
if (res.getRefundTxId != null)
|
|
1256
|
+
this._refundTxId = await res.getRefundTxId();
|
|
1257
|
+
await this._saveAndEmit(FromBTCSwapState.FAILED);
|
|
1258
|
+
}
|
|
1259
|
+
throw new Error("Swap expired while waiting for claim!");
|
|
1260
|
+
}
|
|
1261
|
+
return true;
|
|
1262
|
+
}
|
|
1263
|
+
//////////////////////////////
|
|
1264
|
+
//// Storage
|
|
1265
|
+
/**
|
|
1266
|
+
* @inheritDoc
|
|
1267
|
+
*/
|
|
1268
|
+
serialize() {
|
|
1269
|
+
return {
|
|
1270
|
+
...super.serialize(),
|
|
1271
|
+
address: this.address,
|
|
1272
|
+
amount: this.amount == null ? null : this.amount.toString(10),
|
|
1273
|
+
requiredConfirmations: this.requiredConfirmations,
|
|
1274
|
+
senderAddress: this.senderAddress,
|
|
1275
|
+
txId: this.txId,
|
|
1276
|
+
vout: this.vout,
|
|
1277
|
+
btcTxConfirmedAt: this.btcTxConfirmedAt
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
//////////////////////////////
|
|
1281
|
+
//// Swap ticks & sync
|
|
1282
|
+
/**
|
|
1283
|
+
* Checks the swap's state on-chain and compares it to its internal state, updates/changes it according to on-chain
|
|
1284
|
+
* data
|
|
1285
|
+
*
|
|
1286
|
+
* @private
|
|
1287
|
+
*/
|
|
1288
|
+
async syncStateFromChain(quoteDefinitelyExpired, commitStatus) {
|
|
1289
|
+
if (this._state === FromBTCSwapState.PR_CREATED ||
|
|
1290
|
+
this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
|
|
1291
|
+
this._state === FromBTCSwapState.CLAIM_COMMITED ||
|
|
1292
|
+
this._state === FromBTCSwapState.BTC_TX_CONFIRMED ||
|
|
1293
|
+
this._state === FromBTCSwapState.EXPIRED) {
|
|
1294
|
+
let quoteExpired = false;
|
|
1295
|
+
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1296
|
+
quoteExpired = quoteDefinitelyExpired ?? await this._verifyQuoteDefinitelyExpired(); //Make sure we check for expiry here, to prevent race conditions
|
|
1297
|
+
}
|
|
1298
|
+
const status = commitStatus ?? await this._contract.getCommitStatus(this._getInitiator(), this._data);
|
|
1299
|
+
if (status != null && await this._forciblySetOnchainState(status))
|
|
1300
|
+
return true;
|
|
1301
|
+
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1302
|
+
if (quoteExpired) {
|
|
1303
|
+
this._state = FromBTCSwapState.QUOTE_EXPIRED;
|
|
1304
|
+
return true;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
return false;
|
|
1309
|
+
}
|
|
1310
|
+
/**
|
|
1311
|
+
* @inheritDoc
|
|
1312
|
+
* @internal
|
|
1313
|
+
*/
|
|
1314
|
+
_shouldFetchOnchainState() {
|
|
1315
|
+
return this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
|
|
1316
|
+
this._state === FromBTCSwapState.CLAIM_COMMITED || this._state === FromBTCSwapState.BTC_TX_CONFIRMED ||
|
|
1317
|
+
this._state === FromBTCSwapState.EXPIRED;
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* @inheritDoc
|
|
1321
|
+
* @internal
|
|
1322
|
+
*/
|
|
1323
|
+
_shouldFetchExpiryStatus() {
|
|
1324
|
+
return this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED;
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* @inheritDoc
|
|
1328
|
+
* @internal
|
|
1329
|
+
*/
|
|
1330
|
+
async _sync(save, quoteDefinitelyExpired, commitStatus) {
|
|
1331
|
+
const changed = await this.syncStateFromChain(quoteDefinitelyExpired, commitStatus);
|
|
1332
|
+
if (changed && save)
|
|
1333
|
+
await this._saveAndEmit();
|
|
1334
|
+
return changed;
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* @inheritDoc
|
|
1338
|
+
* @internal
|
|
1339
|
+
*/
|
|
1340
|
+
async _forciblySetOnchainState(status) {
|
|
1341
|
+
switch (status.type) {
|
|
1342
|
+
case base_1.SwapCommitStateType.PAID:
|
|
1343
|
+
if (this._commitTxId == null && status.getInitTxId != null)
|
|
1344
|
+
this._commitTxId = await status.getInitTxId();
|
|
1345
|
+
if (this._claimTxId == null)
|
|
1346
|
+
this._claimTxId = await status.getClaimTxId();
|
|
1347
|
+
const txId = buffer_1.Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
|
|
1348
|
+
await this._setBitcoinTxId(txId);
|
|
1349
|
+
this._state = FromBTCSwapState.CLAIM_CLAIMED;
|
|
1350
|
+
return true;
|
|
1351
|
+
case base_1.SwapCommitStateType.NOT_COMMITED:
|
|
1352
|
+
let changed = false;
|
|
1353
|
+
if (this._commitTxId == null && status.getInitTxId != null) {
|
|
1354
|
+
this._commitTxId = await status.getInitTxId();
|
|
1355
|
+
changed = true;
|
|
1356
|
+
}
|
|
1357
|
+
if (this._refundTxId == null && status.getRefundTxId) {
|
|
1358
|
+
this._refundTxId = await status.getRefundTxId();
|
|
1359
|
+
changed = true;
|
|
1360
|
+
}
|
|
1361
|
+
if (this._refundTxId != null) {
|
|
1362
|
+
this._state = FromBTCSwapState.FAILED;
|
|
1363
|
+
changed = true;
|
|
1364
|
+
}
|
|
1365
|
+
return changed;
|
|
1366
|
+
case base_1.SwapCommitStateType.EXPIRED:
|
|
1367
|
+
if (this._commitTxId == null && status.getInitTxId != null)
|
|
1368
|
+
this._commitTxId = await status.getInitTxId();
|
|
1369
|
+
if (this._refundTxId == null && status.getRefundTxId)
|
|
1370
|
+
this._refundTxId = await status.getRefundTxId();
|
|
1371
|
+
this._state = this._refundTxId == null ? FromBTCSwapState.QUOTE_EXPIRED : FromBTCSwapState.FAILED;
|
|
1372
|
+
return true;
|
|
1373
|
+
case base_1.SwapCommitStateType.COMMITED:
|
|
1374
|
+
let save = false;
|
|
1375
|
+
if (this._commitTxId == null && status.getInitTxId != null) {
|
|
1376
|
+
this._commitTxId = await status.getInitTxId();
|
|
1377
|
+
save = true;
|
|
1378
|
+
}
|
|
1379
|
+
if (this._state !== FromBTCSwapState.CLAIM_COMMITED && this._state !== FromBTCSwapState.BTC_TX_CONFIRMED && this._state !== FromBTCSwapState.EXPIRED) {
|
|
1380
|
+
this._state = FromBTCSwapState.CLAIM_COMMITED;
|
|
1381
|
+
save = true;
|
|
1382
|
+
}
|
|
1383
|
+
if (this.address == null)
|
|
1384
|
+
return save;
|
|
1385
|
+
this.btcTxLastChecked = Date.now();
|
|
1386
|
+
const res = await this.getBitcoinPayment();
|
|
1387
|
+
if (res != null) {
|
|
1388
|
+
if (this.txId !== res.txId || this.vout !== res.vout || (res.inputAddresses != null && this.senderAddress == null)) {
|
|
1389
|
+
if (res.inputAddresses != null)
|
|
1390
|
+
this.senderAddress = res.inputAddresses[0];
|
|
1391
|
+
this.txId = res.txId;
|
|
1392
|
+
this.vout = res.vout;
|
|
1393
|
+
save = true;
|
|
1394
|
+
}
|
|
1395
|
+
if (this.requiredConfirmations != null && res.confirmations >= this.requiredConfirmations) {
|
|
1396
|
+
this.btcTxConfirmedAt ??= Date.now();
|
|
1397
|
+
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
1398
|
+
save = true;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
return save;
|
|
1402
|
+
}
|
|
1403
|
+
return false;
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* @inheritDoc
|
|
1407
|
+
* @internal
|
|
1408
|
+
*/
|
|
1409
|
+
async _tick(save) {
|
|
1410
|
+
switch (this._state) {
|
|
1411
|
+
case FromBTCSwapState.PR_CREATED:
|
|
1412
|
+
if (this.expiry < Date.now()) {
|
|
1413
|
+
this._state = FromBTCSwapState.QUOTE_SOFT_EXPIRED;
|
|
1414
|
+
if (save)
|
|
1415
|
+
await this._saveAndEmit();
|
|
1416
|
+
return true;
|
|
1417
|
+
}
|
|
1418
|
+
break;
|
|
1419
|
+
case FromBTCSwapState.CLAIM_COMMITED:
|
|
1420
|
+
if (this.getTimeoutTime() < Date.now()) {
|
|
1421
|
+
this._state = FromBTCSwapState.EXPIRED;
|
|
1422
|
+
if (save)
|
|
1423
|
+
await this._saveAndEmit();
|
|
1424
|
+
return true;
|
|
1425
|
+
}
|
|
1426
|
+
case FromBTCSwapState.EXPIRED:
|
|
1427
|
+
//Check if bitcoin payment was received at least every 2 minutes
|
|
1428
|
+
if (this.btcTxLastChecked == null || Date.now() - this.btcTxLastChecked > 120000) {
|
|
1429
|
+
if (this.address != null)
|
|
1430
|
+
try {
|
|
1431
|
+
this.btcTxLastChecked = Date.now();
|
|
1432
|
+
const res = await this.getBitcoinPayment();
|
|
1433
|
+
if (res != null) {
|
|
1434
|
+
let shouldSave = false;
|
|
1435
|
+
if (this.txId !== res.txId || this.vout !== res.vout || (res.inputAddresses != null && this.senderAddress == null)) {
|
|
1436
|
+
this.txId = res.txId;
|
|
1437
|
+
this.vout = res.vout;
|
|
1438
|
+
if (res.inputAddresses != null)
|
|
1439
|
+
this.senderAddress = res.inputAddresses[0];
|
|
1440
|
+
shouldSave = true;
|
|
1441
|
+
}
|
|
1442
|
+
if (this.requiredConfirmations != null && res.confirmations >= this.requiredConfirmations) {
|
|
1443
|
+
this.btcTxConfirmedAt ??= Date.now();
|
|
1444
|
+
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
1445
|
+
if (save)
|
|
1446
|
+
await this._saveAndEmit();
|
|
1447
|
+
shouldSave = true;
|
|
1448
|
+
}
|
|
1449
|
+
if (shouldSave && save)
|
|
1450
|
+
await this._saveAndEmit();
|
|
1451
|
+
return shouldSave;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
catch (e) {
|
|
1455
|
+
this.logger.warn("tickSwap(" + this.getIdentifierHashString() + "): ", e);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
break;
|
|
1459
|
+
}
|
|
1460
|
+
return false;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
exports.FromBTCSwap = FromBTCSwap;
|