@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,1883 +1,1883 @@
|
|
|
1
|
-
import {decode as bolt11Decode} from "@atomiqlabs/bolt11";
|
|
2
|
-
import {SwapType} from "../../../../enums/SwapType";
|
|
3
|
-
import {
|
|
4
|
-
ChainSwapType,
|
|
5
|
-
ChainType,
|
|
6
|
-
isAbstractSigner,
|
|
7
|
-
SwapClaimWitnessMessage,
|
|
8
|
-
SwapCommitState,
|
|
9
|
-
SwapCommitStateType,
|
|
10
|
-
SwapData,
|
|
11
|
-
} from "@atomiqlabs/base";
|
|
12
|
-
import {Buffer} from "buffer";
|
|
13
|
-
import {LNURL} from "../../../../lnurl/LNURL";
|
|
14
|
-
import {UserError} from "../../../../errors/UserError";
|
|
15
|
-
import {
|
|
16
|
-
IntermediaryAPI,
|
|
17
|
-
InvoiceStatusResponse,
|
|
18
|
-
InvoiceStatusResponseCodes
|
|
19
|
-
} from "../../../../intermediaries/apis/IntermediaryAPI";
|
|
20
|
-
import {IntermediaryError} from "../../../../errors/IntermediaryError";
|
|
21
|
-
import {extendAbortController, toBigInt} from "../../../../utils/Utils";
|
|
22
|
-
import {Fee} from "../../../../types/fees/Fee";
|
|
23
|
-
import {IAddressSwap} from "../../../IAddressSwap";
|
|
24
|
-
import {FromBTCLNAutoDefinition, FromBTCLNAutoWrapper} from "./FromBTCLNAutoWrapper";
|
|
25
|
-
import {ISwapWithGasDrop} from "../../../ISwapWithGasDrop";
|
|
26
|
-
import {MinimalLightningNetworkWalletInterface} from "../../../../types/wallets/MinimalLightningNetworkWalletInterface";
|
|
27
|
-
import {IClaimableSwap} from "../../../IClaimableSwap";
|
|
28
|
-
import {IEscrowSwap, IEscrowSwapInit, isIEscrowSwapInit} from "../../IEscrowSwap";
|
|
29
|
-
import {FeeType} from "../../../../enums/FeeType";
|
|
30
|
-
import {ppmToPercentage} from "../../../../types/fees/PercentagePPM";
|
|
31
|
-
import {TokenAmount, toTokenAmount} from "../../../../types/TokenAmount";
|
|
32
|
-
import {BitcoinTokens, BtcToken, SCToken} from "../../../../types/Token";
|
|
33
|
-
import {getLogger, LoggerType} from "../../../../utils/Logger";
|
|
34
|
-
import {timeoutPromise} from "../../../../utils/TimeoutUtils";
|
|
35
|
-
import {isLNURLWithdraw, LNURLWithdraw, LNURLWithdrawParamsWithUrl} from "../../../../types/lnurl/LNURLWithdraw";
|
|
36
|
-
import {
|
|
37
|
-
deserializePriceInfoType,
|
|
38
|
-
isPriceInfoType,
|
|
39
|
-
PriceInfoType,
|
|
40
|
-
serializePriceInfoType
|
|
41
|
-
} from "../../../../types/PriceInfoType";
|
|
42
|
-
import {sha256} from "@noble/hashes/sha2";
|
|
43
|
-
import {
|
|
44
|
-
SwapExecutionActionSendToAddress,
|
|
45
|
-
SwapExecutionActionSignSmartChainTx,
|
|
46
|
-
SwapExecutionActionWait
|
|
47
|
-
} from "../../../../types/SwapExecutionAction";
|
|
48
|
-
import {
|
|
49
|
-
SwapExecutionStepPayment,
|
|
50
|
-
SwapExecutionStepSettlement
|
|
51
|
-
} from "../../../../types/SwapExecutionStep";
|
|
52
|
-
import {SwapStateInfo} from "../../../../types/SwapStateInfo";
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* State enum for FromBTCLNAuto swaps
|
|
56
|
-
* @category Swaps/Lightning → Smart chain
|
|
57
|
-
*/
|
|
58
|
-
export enum FromBTCLNAutoSwapState {
|
|
59
|
-
/**
|
|
60
|
-
* Swap has failed as the user didn't settle the HTLC on the destination before expiration
|
|
61
|
-
*/
|
|
62
|
-
FAILED = -4,
|
|
63
|
-
/**
|
|
64
|
-
* Swap has expired for good and there is no way how it can be executed anymore
|
|
65
|
-
*/
|
|
66
|
-
QUOTE_EXPIRED = -3,
|
|
67
|
-
/**
|
|
68
|
-
* A swap is almost expired, and it should be presented to the user as expired, though
|
|
69
|
-
* there is still a chance that it will be processed
|
|
70
|
-
*/
|
|
71
|
-
QUOTE_SOFT_EXPIRED = -2,
|
|
72
|
-
/**
|
|
73
|
-
* Swap HTLC on the destination chain has expired, it is not safe anymore to settle (claim) the
|
|
74
|
-
* swap on the destination smart chain.
|
|
75
|
-
*/
|
|
76
|
-
EXPIRED = -1,
|
|
77
|
-
/**
|
|
78
|
-
* Swap quote was created, use {@link FromBTCLNAutoSwap.getAddress} or {@link FromBTCLNAutoSwap.getHyperlink}
|
|
79
|
-
* to get the bolt11 lightning network invoice to pay to initiate the swap, then use the
|
|
80
|
-
* {@link FromBTCLNAutoSwap.waitForPayment} to wait till the lightning network payment is received
|
|
81
|
-
* by the intermediary (LP) and the destination HTLC escrow is created
|
|
82
|
-
*/
|
|
83
|
-
PR_CREATED = 0,
|
|
84
|
-
/**
|
|
85
|
-
* Lightning network payment has been received by the intermediary (LP), but the destination chain
|
|
86
|
-
* HTLC escrow hasn't been created yet. Use {@link FromBTCLNAutoSwap.waitForPayment} to continue waiting
|
|
87
|
-
* till the destination HTLC escrow is created.
|
|
88
|
-
*/
|
|
89
|
-
PR_PAID = 1,
|
|
90
|
-
/**
|
|
91
|
-
* Swap escrow HTLC has been created on the destination chain, wait for automatic settlement by the watchtowers
|
|
92
|
-
* using the {@link FromBTCLNAutoSwap.waitTillClaimed} function or settle manually using the
|
|
93
|
-
* {@link FromBTCLNAutoSwap.claim} or {@link FromBTCLNAutoSwap.txsClaim} function.
|
|
94
|
-
*/
|
|
95
|
-
CLAIM_COMMITED = 2,
|
|
96
|
-
/**
|
|
97
|
-
* Swap successfully settled and funds received on the destination chain
|
|
98
|
-
*/
|
|
99
|
-
CLAIM_CLAIMED = 3
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const FromBTCLNAutoSwapStateDescription = {
|
|
103
|
-
[FromBTCLNAutoSwapState.FAILED]: "Swap has failed as the user didn't settle the HTLC on the destination before expiration",
|
|
104
|
-
[FromBTCLNAutoSwapState.QUOTE_EXPIRED]: "Swap has expired for good and there is no way how it can be executed anymore",
|
|
105
|
-
[FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED]: "A swap is expired, though there is still a chance that it will be processed",
|
|
106
|
-
[FromBTCLNAutoSwapState.EXPIRED]: "Swap HTLC on the destination chain has expired, it is not safe anymore to settle (claim) the swap on the destination smart chain.",
|
|
107
|
-
[FromBTCLNAutoSwapState.PR_CREATED]: "Swap quote was created, pay the bolt11 lightning network invoice to initiate the swap, then wait till the lightning network payment is received by the intermediary (LP) and the destination HTLC escrow is created",
|
|
108
|
-
[FromBTCLNAutoSwapState.PR_PAID]: "Lightning network payment has been received by the intermediary (LP), but the destination chain HTLC escrow hasn't been created yet. Continue waiting till the destination HTLC escrow is created.",
|
|
109
|
-
[FromBTCLNAutoSwapState.CLAIM_COMMITED]: "Swap escrow HTLC has been created on the destination chain, wait for automatic settlement by the watchtowers or settle manually.",
|
|
110
|
-
[FromBTCLNAutoSwapState.CLAIM_CLAIMED]: "Swap successfully settled and funds received on the destination chain"
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
export type FromBTCLNAutoSwapInit<T extends SwapData> = IEscrowSwapInit<T> & {
|
|
114
|
-
pr?: string,
|
|
115
|
-
secret?: string,
|
|
116
|
-
initialSwapData: T,
|
|
117
|
-
|
|
118
|
-
btcAmountSwap?: bigint,
|
|
119
|
-
btcAmountGas?: bigint,
|
|
120
|
-
|
|
121
|
-
gasSwapFeeBtc: bigint,
|
|
122
|
-
gasSwapFee: bigint,
|
|
123
|
-
gasPricingInfo?: PriceInfoType,
|
|
124
|
-
|
|
125
|
-
lnurl?: string,
|
|
126
|
-
lnurlK1?: string,
|
|
127
|
-
lnurlCallback?: string
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
export function isFromBTCLNAutoSwapInit<T extends SwapData>(obj: any): obj is FromBTCLNAutoSwapInit<T> {
|
|
131
|
-
return (obj.pr==null || typeof obj.pr==="string") &&
|
|
132
|
-
(obj.secret==null || typeof obj.secret==="string") &&
|
|
133
|
-
(obj.btcAmountSwap==null || typeof obj.btcAmountSwap==="bigint") &&
|
|
134
|
-
(obj.btcAmountGas==null || typeof obj.btcAmountGas==="bigint") &&
|
|
135
|
-
typeof obj.gasSwapFeeBtc==="bigint" &&
|
|
136
|
-
typeof obj.gasSwapFee==="bigint" &&
|
|
137
|
-
(obj.gasPricingInfo==null || isPriceInfoType(obj.gasPricingInfo)) &&
|
|
138
|
-
(obj.lnurl==null || typeof(obj.lnurl)==="string") &&
|
|
139
|
-
(obj.lnurlK1==null || typeof(obj.lnurlK1)==="string") &&
|
|
140
|
-
(obj.lnurlCallback==null || typeof(obj.lnurlCallback)==="string") &&
|
|
141
|
-
isIEscrowSwapInit(obj);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* New escrow based (HTLC) swaps for Bitcoin Lightning -> Smart chain swaps not requiring manual settlement on
|
|
146
|
-
* the destination by the user, and instead letting the LP initiate the escrow. Permissionless watchtower network
|
|
147
|
-
* handles the claiming of HTLC, with the swap secret broadcasted over Nostr. Also adds a possibility for the user
|
|
148
|
-
* to receive a native token on the destination chain as part of the swap (a "gas drop" feature).
|
|
149
|
-
*
|
|
150
|
-
* @category Swaps/Lightning → Smart chain
|
|
151
|
-
*/
|
|
152
|
-
export class FromBTCLNAutoSwap<T extends ChainType = ChainType>
|
|
153
|
-
extends IEscrowSwap<T, FromBTCLNAutoDefinition<T>>
|
|
154
|
-
implements IAddressSwap, ISwapWithGasDrop<T>, IClaimableSwap<T, FromBTCLNAutoDefinition<T>, FromBTCLNAutoSwapState> {
|
|
155
|
-
|
|
156
|
-
protected readonly TYPE: SwapType.FROM_BTCLN_AUTO = SwapType.FROM_BTCLN_AUTO;
|
|
157
|
-
/**
|
|
158
|
-
* @internal
|
|
159
|
-
*/
|
|
160
|
-
protected readonly swapStateName = (state: number) => FromBTCLNAutoSwapState[state];
|
|
161
|
-
/**
|
|
162
|
-
* @internal
|
|
163
|
-
*/
|
|
164
|
-
protected readonly swapStateDescription = FromBTCLNAutoSwapStateDescription;
|
|
165
|
-
/**
|
|
166
|
-
* @internal
|
|
167
|
-
*/
|
|
168
|
-
protected readonly logger: LoggerType;
|
|
169
|
-
/**
|
|
170
|
-
* @internal
|
|
171
|
-
*/
|
|
172
|
-
protected readonly inputToken: BtcToken<true> = BitcoinTokens.BTCLN;
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Timestamp at which the HTLC was commited on the smart chain side
|
|
176
|
-
* @internal
|
|
177
|
-
*/
|
|
178
|
-
_commitedAt?: number;
|
|
179
|
-
|
|
180
|
-
private readonly lnurlFailSignal: AbortController = new AbortController();
|
|
181
|
-
private readonly usesClaimHashAsId: boolean;
|
|
182
|
-
private readonly initialSwapData: T["Data"];
|
|
183
|
-
|
|
184
|
-
private readonly btcAmountSwap?: bigint;
|
|
185
|
-
private readonly btcAmountGas?: bigint;
|
|
186
|
-
|
|
187
|
-
private readonly gasSwapFeeBtc: bigint;
|
|
188
|
-
private readonly gasSwapFee: bigint;
|
|
189
|
-
|
|
190
|
-
private readonly gasPricingInfo?: PriceInfoType;
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* In case the swap is recovered from on-chain data, the pr saved here is just a payment hash,
|
|
194
|
-
* as it is impossible to retrieve the actual lightning network invoice paid purely from on-chain
|
|
195
|
-
* data
|
|
196
|
-
* @private
|
|
197
|
-
*/
|
|
198
|
-
private pr?: string;
|
|
199
|
-
private secret?: string;
|
|
200
|
-
|
|
201
|
-
private lnurl?: string;
|
|
202
|
-
private lnurlK1?: string;
|
|
203
|
-
private lnurlCallback?: string;
|
|
204
|
-
private prPosted?: boolean = false;
|
|
205
|
-
|
|
206
|
-
private broadcastTickCounter: number = 0
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Sets the LNURL data for the swap
|
|
210
|
-
*
|
|
211
|
-
* @internal
|
|
212
|
-
*/
|
|
213
|
-
_setLNURLData(lnurl: string, lnurlK1: string, lnurlCallback: string) {
|
|
214
|
-
this.lnurl = lnurl;
|
|
215
|
-
this.lnurlK1 = lnurlK1;
|
|
216
|
-
this.lnurlCallback = lnurlCallback;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
constructor(wrapper: FromBTCLNAutoWrapper<T>, init: FromBTCLNAutoSwapInit<T["Data"]>);
|
|
220
|
-
constructor(wrapper: FromBTCLNAutoWrapper<T>, obj: any);
|
|
221
|
-
constructor(
|
|
222
|
-
wrapper: FromBTCLNAutoWrapper<T>,
|
|
223
|
-
initOrObject: FromBTCLNAutoSwapInit<T["Data"]> | any
|
|
224
|
-
) {
|
|
225
|
-
if(isFromBTCLNAutoSwapInit(initOrObject) && initOrObject.url!=null) initOrObject.url += "/frombtcln_auto";
|
|
226
|
-
super(wrapper, initOrObject);
|
|
227
|
-
if(isFromBTCLNAutoSwapInit(initOrObject)) {
|
|
228
|
-
this._state = FromBTCLNAutoSwapState.PR_CREATED;
|
|
229
|
-
this.pr = initOrObject.pr;
|
|
230
|
-
this.secret = initOrObject.secret;
|
|
231
|
-
this.initialSwapData = initOrObject.initialSwapData;
|
|
232
|
-
this.btcAmountSwap = initOrObject.btcAmountSwap;
|
|
233
|
-
this.btcAmountGas = initOrObject.btcAmountGas;
|
|
234
|
-
this.gasSwapFeeBtc = initOrObject.gasSwapFeeBtc;
|
|
235
|
-
this.gasSwapFee = initOrObject.gasSwapFee;
|
|
236
|
-
this.gasPricingInfo = initOrObject.gasPricingInfo;
|
|
237
|
-
this.lnurl = initOrObject.lnurl;
|
|
238
|
-
this.lnurlK1 = initOrObject.lnurlK1;
|
|
239
|
-
this.lnurlCallback = initOrObject.lnurlCallback;
|
|
240
|
-
this.usesClaimHashAsId = true;
|
|
241
|
-
} else {
|
|
242
|
-
this.pr = initOrObject.pr;
|
|
243
|
-
this.secret = initOrObject.secret;
|
|
244
|
-
|
|
245
|
-
if(initOrObject.initialSwapData==null) {
|
|
246
|
-
this.initialSwapData = this._data!;
|
|
247
|
-
} else {
|
|
248
|
-
this.initialSwapData = SwapData.deserialize<T["Data"]>(initOrObject.initialSwapData);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
this.btcAmountSwap = toBigInt(initOrObject.btcAmountSwap);
|
|
252
|
-
this.btcAmountGas = toBigInt(initOrObject.btcAmountGas);
|
|
253
|
-
this.gasSwapFeeBtc = toBigInt(initOrObject.gasSwapFeeBtc);
|
|
254
|
-
this.gasSwapFee = toBigInt(initOrObject.gasSwapFee);
|
|
255
|
-
this.gasPricingInfo = deserializePriceInfoType(initOrObject.gasPricingInfo);
|
|
256
|
-
|
|
257
|
-
this._commitTxId = initOrObject.commitTxId;
|
|
258
|
-
this._claimTxId = initOrObject.claimTxId;
|
|
259
|
-
this._commitedAt = initOrObject.commitedAt;
|
|
260
|
-
|
|
261
|
-
this.lnurl = initOrObject.lnurl;
|
|
262
|
-
this.lnurlK1 = initOrObject.lnurlK1;
|
|
263
|
-
this.lnurlCallback = initOrObject.lnurlCallback;
|
|
264
|
-
this.prPosted = initOrObject.prPosted;
|
|
265
|
-
this.usesClaimHashAsId = initOrObject.usesClaimHashAsId ?? false;
|
|
266
|
-
}
|
|
267
|
-
this.tryRecomputeSwapPrice();
|
|
268
|
-
this.logger = getLogger("FromBTCLNAuto("+this.getIdentifierHashString()+"): ");
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* @inheritDoc
|
|
273
|
-
* @internal
|
|
274
|
-
*/
|
|
275
|
-
protected getSwapData(): T["Data"] {
|
|
276
|
-
return this._data ?? this.initialSwapData;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* @inheritDoc
|
|
281
|
-
* @internal
|
|
282
|
-
*/
|
|
283
|
-
protected upgradeVersion() { /*NOOP*/ }
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* @inheritDoc
|
|
287
|
-
* @internal
|
|
288
|
-
*/
|
|
289
|
-
protected tryRecomputeSwapPrice() {
|
|
290
|
-
if(this.pricingInfo==null || this.btcAmountSwap==null) return;
|
|
291
|
-
if(this.pricingInfo.swapPriceUSatPerToken==null) {
|
|
292
|
-
const priceUsdPerBtc = this.pricingInfo.realPriceUsdPerBitcoin;
|
|
293
|
-
this.pricingInfo = this.wrapper._prices.recomputePriceInfoReceive(
|
|
294
|
-
this.chainIdentifier,
|
|
295
|
-
this.btcAmountSwap,
|
|
296
|
-
this.pricingInfo.satsBaseFee,
|
|
297
|
-
this.pricingInfo.feePPM,
|
|
298
|
-
this.getOutputAmountWithoutFee(),
|
|
299
|
-
this.getSwapData().getToken()
|
|
300
|
-
);
|
|
301
|
-
this.pricingInfo.realPriceUsdPerBitcoin = priceUsdPerBtc;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
//////////////////////////////
|
|
307
|
-
//// Pricing
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* @inheritDoc
|
|
311
|
-
*/
|
|
312
|
-
async refreshPriceData(): Promise<void> {
|
|
313
|
-
if(this.pricingInfo==null || this.btcAmountSwap==null) return;
|
|
314
|
-
const usdPricePerBtc = this.pricingInfo.realPriceUsdPerBitcoin;
|
|
315
|
-
this.pricingInfo = await this.wrapper._prices.isValidAmountReceive(
|
|
316
|
-
this.chainIdentifier,
|
|
317
|
-
this.btcAmountSwap,
|
|
318
|
-
this.pricingInfo.satsBaseFee,
|
|
319
|
-
this.pricingInfo.feePPM,
|
|
320
|
-
this.getOutputAmountWithoutFee(),
|
|
321
|
-
this.getSwapData().getToken(),
|
|
322
|
-
undefined,
|
|
323
|
-
undefined,
|
|
324
|
-
this.swapFeeBtc
|
|
325
|
-
);
|
|
326
|
-
this.pricingInfo.realPriceUsdPerBitcoin = usdPricePerBtc;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
//////////////////////////////
|
|
331
|
-
//// Getters & utils
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* @inheritDoc
|
|
335
|
-
* @internal
|
|
336
|
-
*/
|
|
337
|
-
_getEscrowHash(): string | null {
|
|
338
|
-
//Use claim hash in case the data is not yet known
|
|
339
|
-
return this._data == null ? this.initialSwapData?.getClaimHash() : this._data?.getEscrowHash();
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* @inheritDoc
|
|
344
|
-
* @internal
|
|
345
|
-
*/
|
|
346
|
-
_getInitiator(): string {
|
|
347
|
-
return this.getSwapData().getClaimer();
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* @inheritDoc
|
|
352
|
-
*/
|
|
353
|
-
getId(): string {
|
|
354
|
-
return this.getIdentifierHashString();
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* @inheritDoc
|
|
359
|
-
*/
|
|
360
|
-
getOutputAddress(): string | null {
|
|
361
|
-
return this._getInitiator();
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* @inheritDoc
|
|
366
|
-
*/
|
|
367
|
-
getOutputTxId(): string | null {
|
|
368
|
-
return this._claimTxId ?? null;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* @inheritDoc
|
|
373
|
-
*/
|
|
374
|
-
requiresAction(): boolean {
|
|
375
|
-
return this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* @inheritDoc
|
|
380
|
-
* @internal
|
|
381
|
-
*/
|
|
382
|
-
protected getIdentifierHashString(): string {
|
|
383
|
-
const id: string = this.usesClaimHashAsId
|
|
384
|
-
? this.getClaimHash()
|
|
385
|
-
: this.getPaymentHash()!.toString("hex");
|
|
386
|
-
if(this._randomNonce==null) return id;
|
|
387
|
-
return id + this._randomNonce;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Returns the payment hash of the swap and lightning network invoice, or `null` if not known (i.e. if
|
|
392
|
-
* the swap was recovered from on-chain data, the payment hash might not be known)
|
|
393
|
-
*
|
|
394
|
-
* @internal
|
|
395
|
-
*/
|
|
396
|
-
protected getPaymentHash(): Buffer | null {
|
|
397
|
-
if(this.pr==null) return null;
|
|
398
|
-
if(this.pr.toLowerCase().startsWith("ln")) {
|
|
399
|
-
const parsed = bolt11Decode(this.pr);
|
|
400
|
-
if(parsed.tagsObject.payment_hash==null) throw new Error("Swap invoice has no payment hash field!");
|
|
401
|
-
return Buffer.from(parsed.tagsObject.payment_hash, "hex");
|
|
402
|
-
}
|
|
403
|
-
return Buffer.from(this.pr, "hex");
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* @inheritDoc
|
|
408
|
-
*/
|
|
409
|
-
getInputAddress(): string | null {
|
|
410
|
-
return this.lnurl ?? this.pr ?? null;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* @inheritDoc
|
|
415
|
-
*/
|
|
416
|
-
getInputTxId(): string | null {
|
|
417
|
-
const paymentHash = this.getPaymentHash();
|
|
418
|
-
if(paymentHash==null) return null;
|
|
419
|
-
return paymentHash.toString("hex");
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* Returns the lightning network BOLT11 invoice that needs to be paid as an input to the swap
|
|
424
|
-
*/
|
|
425
|
-
getAddress(): string {
|
|
426
|
-
return this.pr ?? "";
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* @inheritDoc
|
|
431
|
-
*/
|
|
432
|
-
getHyperlink(): string {
|
|
433
|
-
return this.pr==null ? "" : "lightning:"+this.pr.toUpperCase();
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Returns the timeout time (in UNIX milliseconds) when the swap will definitelly be considered as expired
|
|
438
|
-
* if the LP doesn't make it expired sooner
|
|
439
|
-
*/
|
|
440
|
-
getDefinitiveExpiryTime(): number {
|
|
441
|
-
if(this.pr==null || !this.pr.toLowerCase().startsWith("ln")) return 0;
|
|
442
|
-
const decoded = bolt11Decode(this.pr);
|
|
443
|
-
if(decoded.tagsObject.min_final_cltv_expiry==null) throw new Error("Swap invoice doesn't contain final ctlv delta field!");
|
|
444
|
-
if(decoded.timeExpireDate==null) throw new Error("Swap invoice doesn't contain expiry date field!");
|
|
445
|
-
const finalCltvExpiryDelta = decoded.tagsObject.min_final_cltv_expiry ?? 144;
|
|
446
|
-
const finalCltvExpiryDelay = finalCltvExpiryDelta * this.wrapper._options.bitcoinBlocktime * this.wrapper._options.safetyFactor;
|
|
447
|
-
return (decoded.timeExpireDate + finalCltvExpiryDelay)*1000;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* Returns timeout time (in UNIX milliseconds) when the swap htlc will expire
|
|
452
|
-
*/
|
|
453
|
-
getHtlcTimeoutTime(): number | null {
|
|
454
|
-
return this._data==null ? null : Number(this.wrapper._getHtlcTimeout(this._data))*1000;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* @inheritDoc
|
|
459
|
-
*/
|
|
460
|
-
isFinished(): boolean {
|
|
461
|
-
return this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED || this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED || this._state===FromBTCLNAutoSwapState.FAILED;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/**
|
|
465
|
-
* @inheritDoc
|
|
466
|
-
*/
|
|
467
|
-
isClaimable(): boolean {
|
|
468
|
-
return this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* @inheritDoc
|
|
473
|
-
*/
|
|
474
|
-
isSuccessful(): boolean {
|
|
475
|
-
return this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* @inheritDoc
|
|
480
|
-
*/
|
|
481
|
-
isFailed(): boolean {
|
|
482
|
-
return this._state===FromBTCLNAutoSwapState.FAILED || this._state===FromBTCLNAutoSwapState.EXPIRED;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* @inheritDoc
|
|
487
|
-
*/
|
|
488
|
-
isInProgress(): boolean {
|
|
489
|
-
return (this._state===FromBTCLNAutoSwapState.PR_CREATED && this.initiated) ||
|
|
490
|
-
(this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED && this.initiated) ||
|
|
491
|
-
this._state===FromBTCLNAutoSwapState.PR_PAID ||
|
|
492
|
-
this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* @inheritDoc
|
|
497
|
-
*/
|
|
498
|
-
isQuoteExpired(): boolean {
|
|
499
|
-
return this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* @inheritDoc
|
|
504
|
-
*/
|
|
505
|
-
isQuoteSoftExpired(): boolean {
|
|
506
|
-
return this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* @inheritDoc
|
|
511
|
-
*/
|
|
512
|
-
_verifyQuoteDefinitelyExpired(): Promise<boolean> {
|
|
513
|
-
return Promise.resolve(this.getDefinitiveExpiryTime()<Date.now());
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* @inheritDoc
|
|
518
|
-
*/
|
|
519
|
-
_verifyQuoteValid(): Promise<boolean> {
|
|
520
|
-
return Promise.resolve(this.getQuoteExpiry()>Date.now());
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
//////////////////////////////
|
|
525
|
-
//// Amounts & fees
|
|
526
|
-
|
|
527
|
-
/**
|
|
528
|
-
* Returns the satoshi amount of the lightning network invoice, or `null` if the lightning network
|
|
529
|
-
* invoice is not known (i.e. when the swap was recovered from on-chain data, the paid invoice
|
|
530
|
-
* cannot be recovered because it is purely off-chain)
|
|
531
|
-
*
|
|
532
|
-
* @internal
|
|
533
|
-
*/
|
|
534
|
-
protected getLightningInvoiceSats(): bigint | null {
|
|
535
|
-
if(this.pr==null || !this.pr.toLowerCase().startsWith("ln")) return null;
|
|
536
|
-
|
|
537
|
-
const parsed = bolt11Decode(this.pr);
|
|
538
|
-
if(parsed.millisatoshis==null) throw new Error("Swap invoice doesn't contain msat amount field!");
|
|
539
|
-
return (BigInt(parsed.millisatoshis) + 999n) / 1000n;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* Returns the watchtower fee paid in BTC satoshis, or null if known (i.e. if the swap was recovered from
|
|
544
|
-
* on-chain data)
|
|
545
|
-
*
|
|
546
|
-
* @protected
|
|
547
|
-
*/
|
|
548
|
-
protected getWatchtowerFeeAmountBtc(): bigint | null {
|
|
549
|
-
if(this.btcAmountGas==null) return null;
|
|
550
|
-
return (this.btcAmountGas - this.gasSwapFeeBtc) * this.getSwapData().getClaimerBounty() / this.getSwapData().getTotalDeposit();
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
/**
|
|
554
|
-
* Returns the input amount for the actual swap (excluding the input amount used to cover the "gas drop"
|
|
555
|
-
* part of the swap), excluding fees
|
|
556
|
-
*
|
|
557
|
-
* @internal
|
|
558
|
-
*/
|
|
559
|
-
protected getInputSwapAmountWithoutFee(): bigint | null {
|
|
560
|
-
if(this.btcAmountSwap==null) return null;
|
|
561
|
-
return this.btcAmountSwap - this.swapFeeBtc;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/**
|
|
565
|
-
* Returns the input amount purely for the "gas drop" part of the swap (this much BTC in sats will be
|
|
566
|
-
* swapped into the native gas token on the destination chain), excluding fees
|
|
567
|
-
*
|
|
568
|
-
* @internal
|
|
569
|
-
*/
|
|
570
|
-
protected getInputGasAmountWithoutFee(): bigint | null {
|
|
571
|
-
if(this.btcAmountGas==null) return null;
|
|
572
|
-
return this.btcAmountGas - this.gasSwapFeeBtc;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
/**
|
|
576
|
-
* Get total btc amount in sats on the input, excluding the swap fee and watchtower fee
|
|
577
|
-
*
|
|
578
|
-
* @internal
|
|
579
|
-
*/
|
|
580
|
-
protected getInputAmountWithoutFee(): bigint | null {
|
|
581
|
-
if(this.btcAmountGas==null || this.btcAmountSwap==null) return null;
|
|
582
|
-
return this.getInputSwapAmountWithoutFee()! + this.getInputGasAmountWithoutFee()! - this.getWatchtowerFeeAmountBtc()!;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
/**
|
|
586
|
-
* Returns the "would be" output amount if the swap charged no swap fee
|
|
587
|
-
*
|
|
588
|
-
* @internal
|
|
589
|
-
*/
|
|
590
|
-
protected getOutputAmountWithoutFee(): bigint {
|
|
591
|
-
return this.getSwapData().getAmount() + this.swapFee;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
/**
|
|
595
|
-
* @inheritDoc
|
|
596
|
-
*/
|
|
597
|
-
getInputToken(): BtcToken<true> {
|
|
598
|
-
return BitcoinTokens.BTCLN;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
/**
|
|
602
|
-
* @inheritDoc
|
|
603
|
-
*/
|
|
604
|
-
getInput(): TokenAmount<BtcToken<true>> {
|
|
605
|
-
return toTokenAmount(this.getLightningInvoiceSats(), this.inputToken, this.wrapper._prices, this.pricingInfo);
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
/**
|
|
609
|
-
* @inheritDoc
|
|
610
|
-
*/
|
|
611
|
-
getInputWithoutFee(): TokenAmount<BtcToken<true>> {
|
|
612
|
-
return toTokenAmount(this.getInputAmountWithoutFee(), this.inputToken, this.wrapper._prices, this.pricingInfo);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
/**
|
|
616
|
-
* @inheritDoc
|
|
617
|
-
*/
|
|
618
|
-
getOutputToken(): SCToken<T["ChainId"]> {
|
|
619
|
-
return this.wrapper._tokens[this.getSwapData().getToken()];
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
/**
|
|
623
|
-
* @inheritDoc
|
|
624
|
-
*/
|
|
625
|
-
getOutput(): TokenAmount<SCToken<T["ChainId"]>, true> {
|
|
626
|
-
return toTokenAmount(this.getSwapData().getAmount(), this.wrapper._tokens[this.getSwapData().getToken()], this.wrapper._prices, this.pricingInfo);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
/**
|
|
630
|
-
* @inheritDoc
|
|
631
|
-
*/
|
|
632
|
-
getGasDropOutput(): TokenAmount<SCToken<T["ChainId"]>, true> {
|
|
633
|
-
return toTokenAmount(
|
|
634
|
-
this.getSwapData().getSecurityDeposit() - this.getSwapData().getClaimerBounty(),
|
|
635
|
-
this.wrapper._tokens[this.getSwapData().getDepositToken()], this.wrapper._prices, this.gasPricingInfo
|
|
636
|
-
);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
/**
|
|
640
|
-
* Returns the swap fee charged by the intermediary (LP) on this swap
|
|
641
|
-
*
|
|
642
|
-
* @internal
|
|
643
|
-
*/
|
|
644
|
-
protected getSwapFee(): Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>> {
|
|
645
|
-
if(this.pricingInfo==null) throw new Error("No pricing info known, cannot estimate fee!");
|
|
646
|
-
|
|
647
|
-
const outputToken = this.wrapper._tokens[this.getSwapData().getToken()];
|
|
648
|
-
const gasSwapFeeInOutputToken = this.gasSwapFeeBtc
|
|
649
|
-
* (10n ** BigInt(outputToken.decimals))
|
|
650
|
-
* 1_000_000n
|
|
651
|
-
/ this.pricingInfo.swapPriceUSatPerToken;
|
|
652
|
-
|
|
653
|
-
const feeWithoutBaseFee = this.gasSwapFeeBtc + this.swapFeeBtc - this.pricingInfo.satsBaseFee;
|
|
654
|
-
const inputSats = this.getLightningInvoiceSats();
|
|
655
|
-
const swapFeePPM = inputSats!=null
|
|
656
|
-
? feeWithoutBaseFee * 1000000n / (inputSats - this.swapFeeBtc - this.gasSwapFeeBtc)
|
|
657
|
-
: 0n;
|
|
658
|
-
|
|
659
|
-
const amountInSrcToken = toTokenAmount(this.swapFeeBtc + this.gasSwapFeeBtc, BitcoinTokens.BTCLN, this.wrapper._prices, this.pricingInfo);
|
|
660
|
-
return {
|
|
661
|
-
amountInSrcToken,
|
|
662
|
-
amountInDstToken: toTokenAmount(this.swapFee + gasSwapFeeInOutputToken, outputToken, this.wrapper._prices, this.pricingInfo),
|
|
663
|
-
currentUsdValue: amountInSrcToken.currentUsdValue,
|
|
664
|
-
pastUsdValue: amountInSrcToken.pastUsdValue,
|
|
665
|
-
usdValue: amountInSrcToken.usdValue,
|
|
666
|
-
composition: {
|
|
667
|
-
base: toTokenAmount(this.pricingInfo.satsBaseFee, BitcoinTokens.BTCLN, this.wrapper._prices, this.pricingInfo),
|
|
668
|
-
percentage: ppmToPercentage(swapFeePPM)
|
|
669
|
-
}
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
/**
|
|
674
|
-
* Returns the fee to be paid to watchtowers on the destination chain to automatically
|
|
675
|
-
* process and settle this swap without requiring any user interaction
|
|
676
|
-
*
|
|
677
|
-
* @internal
|
|
678
|
-
*/
|
|
679
|
-
protected getWatchtowerFee(): Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>> {
|
|
680
|
-
if(this.pricingInfo==null) throw new Error("No pricing info known, cannot estimate fee!");
|
|
681
|
-
|
|
682
|
-
const btcWatchtowerFee = this.getWatchtowerFeeAmountBtc();
|
|
683
|
-
const outputToken = this.wrapper._tokens[this.getSwapData().getToken()];
|
|
684
|
-
const watchtowerFeeInOutputToken = btcWatchtowerFee==null ? 0n : btcWatchtowerFee
|
|
685
|
-
* (10n ** BigInt(outputToken.decimals))
|
|
686
|
-
* 1_000_000n
|
|
687
|
-
/ this.pricingInfo.swapPriceUSatPerToken;
|
|
688
|
-
|
|
689
|
-
const amountInSrcToken = toTokenAmount(btcWatchtowerFee, BitcoinTokens.BTCLN, this.wrapper._prices, this.pricingInfo);
|
|
690
|
-
return {
|
|
691
|
-
amountInSrcToken,
|
|
692
|
-
amountInDstToken: toTokenAmount(watchtowerFeeInOutputToken, outputToken, this.wrapper._prices, this.pricingInfo),
|
|
693
|
-
currentUsdValue: amountInSrcToken.currentUsdValue,
|
|
694
|
-
usdValue: amountInSrcToken.usdValue,
|
|
695
|
-
pastUsdValue: amountInSrcToken.pastUsdValue
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
/**
|
|
700
|
-
* @inheritDoc
|
|
701
|
-
*/
|
|
702
|
-
getFee(): Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>> {
|
|
703
|
-
const swapFee = this.getSwapFee();
|
|
704
|
-
const watchtowerFee = this.getWatchtowerFee();
|
|
705
|
-
|
|
706
|
-
const amountInSrcToken = toTokenAmount(
|
|
707
|
-
swapFee.amountInSrcToken.rawAmount + watchtowerFee.amountInSrcToken.rawAmount,
|
|
708
|
-
BitcoinTokens.BTCLN, this.wrapper._prices, this.pricingInfo
|
|
709
|
-
);
|
|
710
|
-
return {
|
|
711
|
-
amountInSrcToken,
|
|
712
|
-
amountInDstToken: toTokenAmount(
|
|
713
|
-
swapFee.amountInDstToken.rawAmount + watchtowerFee.amountInDstToken.rawAmount,
|
|
714
|
-
this.wrapper._tokens[this.getSwapData().getToken()], this.wrapper._prices, this.pricingInfo
|
|
715
|
-
),
|
|
716
|
-
currentUsdValue: amountInSrcToken.currentUsdValue,
|
|
717
|
-
usdValue: amountInSrcToken.usdValue,
|
|
718
|
-
pastUsdValue: amountInSrcToken.pastUsdValue
|
|
719
|
-
};
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
/**
|
|
723
|
-
* @inheritDoc
|
|
724
|
-
*/
|
|
725
|
-
getFeeBreakdown(): [
|
|
726
|
-
{type: FeeType.SWAP, fee: Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>>},
|
|
727
|
-
{type: FeeType.NETWORK_OUTPUT, fee: Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>>}
|
|
728
|
-
] {
|
|
729
|
-
return [
|
|
730
|
-
{
|
|
731
|
-
type: FeeType.SWAP,
|
|
732
|
-
fee: this.getSwapFee()
|
|
733
|
-
},
|
|
734
|
-
{
|
|
735
|
-
type: FeeType.NETWORK_OUTPUT,
|
|
736
|
-
fee: this.getWatchtowerFee()
|
|
737
|
-
}
|
|
738
|
-
];
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
private isValidSecretPreimage(secret: string) {
|
|
742
|
-
const paymentHash = Buffer.from(sha256(Buffer.from(secret, "hex")));
|
|
743
|
-
const claimHash = this._contract.getHashForHtlc(paymentHash).toString("hex");
|
|
744
|
-
return this.getSwapData().getClaimHash()===claimHash;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
/**
|
|
748
|
-
* Sets the secret preimage for the swap, in case it is not known already
|
|
749
|
-
*
|
|
750
|
-
* @param secret Secret preimage that matches the expected payment hash
|
|
751
|
-
*
|
|
752
|
-
* @throws {Error} If an invalid secret preimage is provided
|
|
753
|
-
*/
|
|
754
|
-
async setSecretPreimage(secret: string) {
|
|
755
|
-
if(!this.isValidSecretPreimage(secret)) throw new Error("Invalid secret preimage provided, hash doesn't match!");
|
|
756
|
-
this.secret = secret;
|
|
757
|
-
await this._broadcastSecret().catch(e => this.logger.error("setSecretPreimage(): Failed to broadcast swap secret: ", e));
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
/**
|
|
761
|
-
* Returns whether the secret preimage for this swap is known
|
|
762
|
-
*/
|
|
763
|
-
hasSecretPreimage(): boolean {
|
|
764
|
-
return this.secret != null;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
//////////////////////////////
|
|
769
|
-
//// Execution
|
|
770
|
-
|
|
771
|
-
/**
|
|
772
|
-
* Executes the swap with the provided bitcoin lightning network wallet or LNURL
|
|
773
|
-
*
|
|
774
|
-
* @param walletOrLnurlWithdraw Bitcoin lightning wallet to use to pay the lightning network invoice, or an LNURL-withdraw
|
|
775
|
-
* link, wallet is not required and the LN invoice can be paid externally as well (just pass null or undefined here)
|
|
776
|
-
* @param callbacks Callbacks to track the progress of the swap
|
|
777
|
-
* @param options Optional options for the swap like AbortSignal, and timeouts/intervals
|
|
778
|
-
* @param options.secret A swap secret to broadcast to watchtowers, generally only needed if the swap
|
|
779
|
-
* was recovered from on-chain data, or the pre-image was generated outside the SDK
|
|
780
|
-
*
|
|
781
|
-
* @returns {boolean} Whether a swap was settled automatically by swap watchtowers or requires manual claim by the
|
|
782
|
-
* user, in case `false` is returned the user should call `swap.claim()` to settle the swap on the destination manually
|
|
783
|
-
*/
|
|
784
|
-
async execute(
|
|
785
|
-
walletOrLnurlWithdraw?: MinimalLightningNetworkWalletInterface | LNURLWithdraw | string | null | undefined,
|
|
786
|
-
callbacks?: {
|
|
787
|
-
onSourceTransactionReceived?: (sourceTxId: string) => void,
|
|
788
|
-
onSwapSettled?: (destinationTxId: string) => void
|
|
789
|
-
},
|
|
790
|
-
options?: {
|
|
791
|
-
abortSignal?: AbortSignal,
|
|
792
|
-
lightningTxCheckIntervalSeconds?: number,
|
|
793
|
-
maxWaitTillAutomaticSettlementSeconds?: number,
|
|
794
|
-
secret?: string
|
|
795
|
-
}
|
|
796
|
-
): Promise<boolean> {
|
|
797
|
-
if(this._state===FromBTCLNAutoSwapState.FAILED) throw new Error("Swap failed!");
|
|
798
|
-
if(this._state===FromBTCLNAutoSwapState.EXPIRED) throw new Error("Swap HTLC expired!");
|
|
799
|
-
if(this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) throw new Error("Swap quote expired!");
|
|
800
|
-
if(this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED) throw new Error("Swap already settled!");
|
|
801
|
-
|
|
802
|
-
let abortSignal = options?.abortSignal;
|
|
803
|
-
|
|
804
|
-
if(this._state===FromBTCLNAutoSwapState.PR_CREATED) {
|
|
805
|
-
if(walletOrLnurlWithdraw!=null && this.lnurl==null) {
|
|
806
|
-
if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
|
|
807
|
-
throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
|
|
808
|
-
|
|
809
|
-
if(typeof(walletOrLnurlWithdraw)==="string" || isLNURLWithdraw(walletOrLnurlWithdraw)) {
|
|
810
|
-
await this.settleWithLNURLWithdraw(walletOrLnurlWithdraw);
|
|
811
|
-
} else {
|
|
812
|
-
const paymentPromise = walletOrLnurlWithdraw.payInvoice(this.pr);
|
|
813
|
-
|
|
814
|
-
const abortController = new AbortController();
|
|
815
|
-
paymentPromise.catch(e => abortController.abort(e));
|
|
816
|
-
if(options?.abortSignal!=null) options.abortSignal.addEventListener("abort", () => abortController.abort(options?.abortSignal?.reason));
|
|
817
|
-
abortSignal = abortController.signal;
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.PR_PAID) {
|
|
823
|
-
const paymentSuccess = await this.waitForPayment(callbacks?.onSourceTransactionReceived, options?.lightningTxCheckIntervalSeconds, abortSignal);
|
|
824
|
-
if (!paymentSuccess) throw new Error("Failed to receive lightning network payment");
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
if((this._state as FromBTCLNAutoSwapState)===FromBTCLNAutoSwapState.CLAIM_CLAIMED) return true;
|
|
828
|
-
|
|
829
|
-
if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED) {
|
|
830
|
-
if(this.secret==null && options?.secret==null)
|
|
831
|
-
throw new Error("Tried to wait till settlement, but no secret pre-image is known, please pass the secret pre-image as an argument!");
|
|
832
|
-
const success = await this.waitTillClaimed(options?.maxWaitTillAutomaticSettlementSeconds ?? 60, options?.abortSignal, options?.secret);
|
|
833
|
-
if (success && callbacks?.onSwapSettled != null) callbacks.onSwapSettled(this.getOutputTxId()!);
|
|
834
|
-
return success;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
throw new Error("Invalid state reached!");
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
/**
|
|
841
|
-
* @internal
|
|
842
|
-
*/
|
|
843
|
-
protected async _getExecutionStatus(options?: {
|
|
844
|
-
maxWaitTillAutomaticSettlementSeconds?: number,
|
|
845
|
-
secret?: string
|
|
846
|
-
}) {
|
|
847
|
-
if(options?.secret!=null) await this.setSecretPreimage(options.secret);
|
|
848
|
-
|
|
849
|
-
const state = this._state;
|
|
850
|
-
const now = Date.now();
|
|
851
|
-
|
|
852
|
-
let lightningPaymentStatus: SwapExecutionStepPayment<"LIGHTNING">["status"] = "inactive";
|
|
853
|
-
let destinationSettlementStatus: SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">["status"] = "inactive";
|
|
854
|
-
let buildCurrentAction: (actionOptions?: {
|
|
855
|
-
manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"]
|
|
856
|
-
}) => Promise<
|
|
857
|
-
SwapExecutionActionSendToAddress<true> |
|
|
858
|
-
SwapExecutionActionWait<"LP" | "SETTLEMENT"> |
|
|
859
|
-
SwapExecutionActionSignSmartChainTx<T> |
|
|
860
|
-
undefined
|
|
861
|
-
> = async () => undefined;
|
|
862
|
-
|
|
863
|
-
switch(state) {
|
|
864
|
-
case FromBTCLNAutoSwapState.PR_CREATED: {
|
|
865
|
-
const quoteValid = await this._verifyQuoteValid();
|
|
866
|
-
lightningPaymentStatus = quoteValid ? "awaiting" : "soft_expired";
|
|
867
|
-
if(quoteValid && this.pr!=null && this.pr.toLowerCase().startsWith("ln")) {
|
|
868
|
-
buildCurrentAction = this._buildLightningPaymentAction.bind(this);
|
|
869
|
-
}
|
|
870
|
-
break;
|
|
871
|
-
}
|
|
872
|
-
case FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED:
|
|
873
|
-
lightningPaymentStatus = "soft_expired";
|
|
874
|
-
break;
|
|
875
|
-
case FromBTCLNAutoSwapState.QUOTE_EXPIRED:
|
|
876
|
-
lightningPaymentStatus = "expired";
|
|
877
|
-
break;
|
|
878
|
-
case FromBTCLNAutoSwapState.PR_PAID:
|
|
879
|
-
lightningPaymentStatus = "received";
|
|
880
|
-
destinationSettlementStatus = "waiting_lp";
|
|
881
|
-
buildCurrentAction = this._buildWaitLpAction.bind(this);
|
|
882
|
-
break;
|
|
883
|
-
case FromBTCLNAutoSwapState.CLAIM_COMMITED:
|
|
884
|
-
lightningPaymentStatus = "received";
|
|
885
|
-
if(
|
|
886
|
-
this._commitedAt==null ||
|
|
887
|
-
options?.maxWaitTillAutomaticSettlementSeconds===0 ||
|
|
888
|
-
(now - this._commitedAt) > (options?.maxWaitTillAutomaticSettlementSeconds ?? 60)*1000
|
|
889
|
-
) {
|
|
890
|
-
destinationSettlementStatus = "awaiting_manual";
|
|
891
|
-
if(this.hasSecretPreimage()) {
|
|
892
|
-
//TODO: Maybe add an action that would prompt the user to reveal the pre-image
|
|
893
|
-
buildCurrentAction = this._buildClaimSmartChainTxAction.bind(this);
|
|
894
|
-
}
|
|
895
|
-
} else {
|
|
896
|
-
destinationSettlementStatus = "awaiting_automatic";
|
|
897
|
-
//TODO: Maybe add an action that would prompt the user to reveal the pre-image
|
|
898
|
-
buildCurrentAction = this._buildWaitSettlementAction.bind(this, options?.maxWaitTillAutomaticSettlementSeconds);
|
|
899
|
-
}
|
|
900
|
-
break;
|
|
901
|
-
case FromBTCLNAutoSwapState.CLAIM_CLAIMED:
|
|
902
|
-
lightningPaymentStatus = "confirmed";
|
|
903
|
-
destinationSettlementStatus = "settled";
|
|
904
|
-
break;
|
|
905
|
-
case FromBTCLNAutoSwapState.EXPIRED:
|
|
906
|
-
case FromBTCLNAutoSwapState.FAILED:
|
|
907
|
-
lightningPaymentStatus = "expired";
|
|
908
|
-
destinationSettlementStatus = "expired";
|
|
909
|
-
break;
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
return {
|
|
913
|
-
steps: [
|
|
914
|
-
{
|
|
915
|
-
type: "Payment",
|
|
916
|
-
side: "source",
|
|
917
|
-
chain: "LIGHTNING",
|
|
918
|
-
title: "Lightning payment",
|
|
919
|
-
description: "Pay the Lightning network invoice to initiate the swap",
|
|
920
|
-
status: lightningPaymentStatus,
|
|
921
|
-
initTxId: this.getInputTxId(),
|
|
922
|
-
settleTxId: lightningPaymentStatus==="confirmed" ? this.getInputTxId() : undefined
|
|
923
|
-
},
|
|
924
|
-
{
|
|
925
|
-
type: "Settlement",
|
|
926
|
-
side: "destination",
|
|
927
|
-
chain: this.chainIdentifier,
|
|
928
|
-
title: "Destination settlement",
|
|
929
|
-
description: `Wait for the LP to initiate on the ${this.chainIdentifier} side, then wait for automatic settlement, or settle manually if it takes too long`,
|
|
930
|
-
status: destinationSettlementStatus,
|
|
931
|
-
initTxId: this._commitTxId,
|
|
932
|
-
settleTxId: this._claimTxId
|
|
933
|
-
}
|
|
934
|
-
] as [
|
|
935
|
-
SwapExecutionStepPayment<"LIGHTNING">,
|
|
936
|
-
SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
|
|
937
|
-
],
|
|
938
|
-
buildCurrentAction,
|
|
939
|
-
state
|
|
940
|
-
};
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
/**
|
|
944
|
-
* @internal
|
|
945
|
-
*/
|
|
946
|
-
private async _buildLightningPaymentAction(): Promise<SwapExecutionActionSendToAddress<true>> {
|
|
947
|
-
return {
|
|
948
|
-
type: "SendToAddress",
|
|
949
|
-
name: "Deposit on Lightning",
|
|
950
|
-
description: "Pay the lightning network invoice to initiate the swap",
|
|
951
|
-
chain: "LIGHTNING",
|
|
952
|
-
txs: [{
|
|
953
|
-
type: "BOLT11_PAYMENT_REQUEST",
|
|
954
|
-
address: this.getAddress(),
|
|
955
|
-
hyperlink: this.getHyperlink(),
|
|
956
|
-
amount: this.getInput()
|
|
957
|
-
}],
|
|
958
|
-
waitForTransactions: async (
|
|
959
|
-
maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal
|
|
960
|
-
) => {
|
|
961
|
-
const abortController = extendAbortController(
|
|
962
|
-
abortSignal, maxWaitTimeSeconds, "Timed out waiting for lightning payment"
|
|
963
|
-
);
|
|
964
|
-
const success = await this._waitForLpPaymentReceived(pollIntervalSeconds, abortController.signal);
|
|
965
|
-
if(!success) throw new Error("Quote expired while waiting for Lightning payment");
|
|
966
|
-
|
|
967
|
-
return this.getInputTxId()!;
|
|
968
|
-
}
|
|
969
|
-
} as SwapExecutionActionSendToAddress<true>;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
/**
|
|
973
|
-
* @internal
|
|
974
|
-
*/
|
|
975
|
-
private async _buildWaitLpAction(): Promise<SwapExecutionActionWait<"LP">> {
|
|
976
|
-
return {
|
|
977
|
-
type: "Wait",
|
|
978
|
-
name: "Awaiting LP payout",
|
|
979
|
-
description: "Wait for the LP to create the swap HTLC on the destination smart chain",
|
|
980
|
-
pollTimeSeconds: 5,
|
|
981
|
-
expectedTimeSeconds: 10,
|
|
982
|
-
wait: async (
|
|
983
|
-
maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal
|
|
984
|
-
) => {
|
|
985
|
-
const abortController = extendAbortController(
|
|
986
|
-
abortSignal, maxWaitTimeSeconds, "Timed out waiting for LP payout"
|
|
987
|
-
);
|
|
988
|
-
await this.waitTillCommited(pollIntervalSeconds, abortController.signal);
|
|
989
|
-
}
|
|
990
|
-
} as SwapExecutionActionWait<"LP">;
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
/**
|
|
994
|
-
* @internal
|
|
995
|
-
*/
|
|
996
|
-
private async _buildWaitSettlementAction(
|
|
997
|
-
maxWaitTillAutomaticSettlementSeconds?: number
|
|
998
|
-
): Promise<SwapExecutionActionWait<"SETTLEMENT">> {
|
|
999
|
-
return {
|
|
1000
|
-
type: "Wait",
|
|
1001
|
-
name: "Automatic settlement",
|
|
1002
|
-
description: "Wait for automatic settlement by the watchtower",
|
|
1003
|
-
pollTimeSeconds: 5,
|
|
1004
|
-
expectedTimeSeconds: 10,
|
|
1005
|
-
wait: async (
|
|
1006
|
-
maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal
|
|
1007
|
-
) => {
|
|
1008
|
-
await this.waitTillClaimed(
|
|
1009
|
-
maxWaitTimeSeconds ?? maxWaitTillAutomaticSettlementSeconds ?? 60,
|
|
1010
|
-
abortSignal,
|
|
1011
|
-
undefined,
|
|
1012
|
-
pollIntervalSeconds
|
|
1013
|
-
);
|
|
1014
|
-
}
|
|
1015
|
-
} as SwapExecutionActionWait<"SETTLEMENT">;
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
/**
|
|
1019
|
-
* @inheritDoc
|
|
1020
|
-
* @internal
|
|
1021
|
-
*/
|
|
1022
|
-
async _submitExecutionTransactions(
|
|
1023
|
-
txs: (T["SignedTXType"] | string)[],
|
|
1024
|
-
abortSignal?: AbortSignal,
|
|
1025
|
-
requiredStates?: FromBTCLNAutoSwapState[],
|
|
1026
|
-
idempotent?: boolean
|
|
1027
|
-
): Promise<string[]> {
|
|
1028
|
-
const parsedTxs: T["SignedTXType"][] = [];
|
|
1029
|
-
for(let tx of txs) {
|
|
1030
|
-
parsedTxs.push(typeof(tx)==="string" ? await this.wrapper._chain.deserializeSignedTx(tx) : tx);
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
if(idempotent) {
|
|
1034
|
-
// Handle idempotent calls
|
|
1035
|
-
if(this.wrapper._chain.getTxId!=null) {
|
|
1036
|
-
const txIds = await Promise.all(parsedTxs.map(tx => this.wrapper._chain.getTxId!(tx)));
|
|
1037
|
-
const foundTxId = txIds.find(txId => this._claimTxId===txId);
|
|
1038
|
-
if(foundTxId!=null) return txIds;
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
if(requiredStates!=null && !requiredStates.includes(this._state)) throw new Error("Swap state has changed before transactions were submitted!");
|
|
1043
|
-
|
|
1044
|
-
if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED) {
|
|
1045
|
-
const txIds = await this.wrapper._chain.sendSignedAndConfirm(parsedTxs, true, abortSignal, false);
|
|
1046
|
-
await this.waitTillClaimed(undefined, abortSignal);
|
|
1047
|
-
return txIds;
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
throw new Error("Invalid swap state for transaction submission!");
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
/**
|
|
1054
|
-
* @internal
|
|
1055
|
-
*/
|
|
1056
|
-
private async _buildClaimSmartChainTxAction(actionOptions?: {
|
|
1057
|
-
manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
|
|
1058
|
-
secret?: string
|
|
1059
|
-
}): Promise<SwapExecutionActionSignSmartChainTx<T>> {
|
|
1060
|
-
const signerAddress =
|
|
1061
|
-
await this.wrapper._getSignerAddress(actionOptions?.manualSettlementSmartChainSigner);
|
|
1062
|
-
|
|
1063
|
-
return {
|
|
1064
|
-
type: "SignSmartChainTransaction",
|
|
1065
|
-
name: "Settle manually",
|
|
1066
|
-
description: "Manually settle (claim) the swap on the destination smart chain",
|
|
1067
|
-
chain: this.chainIdentifier,
|
|
1068
|
-
txs: await this.prepareTransactions(this.txsClaim(actionOptions?.manualSettlementSmartChainSigner, actionOptions?.secret)),
|
|
1069
|
-
submitTransactions: async (txs: (T["SignedTXType"] | string)[], abortSignal?: AbortSignal, idempotent?: boolean) => {
|
|
1070
|
-
return this._submitExecutionTransactions(
|
|
1071
|
-
txs,
|
|
1072
|
-
abortSignal,
|
|
1073
|
-
[FromBTCLNAutoSwapState.CLAIM_COMMITED],
|
|
1074
|
-
idempotent
|
|
1075
|
-
);
|
|
1076
|
-
},
|
|
1077
|
-
requiredSigner: signerAddress ?? this._getInitiator()
|
|
1078
|
-
} as SwapExecutionActionSignSmartChainTx<T>;
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
/**
|
|
1082
|
-
*
|
|
1083
|
-
* @param options.manualSettlementSmartChainSigner Optional smart chain signer to create a manual claim (settlement) transaction
|
|
1084
|
-
* @param options.maxWaitTillAutomaticSettlementSeconds Maximum time to wait for an automatic settlement after
|
|
1085
|
-
* the bitcoin transaction is confirmed (defaults to 60 seconds)
|
|
1086
|
-
* @param options.secret A swap secret to broadcast to watchtowers, generally only needed if the swap
|
|
1087
|
-
* was recovered from on-chain data, or the pre-image was generated outside the SDK
|
|
1088
|
-
*/
|
|
1089
|
-
async getExecutionAction(options?: {
|
|
1090
|
-
manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
|
|
1091
|
-
maxWaitTillAutomaticSettlementSeconds?: number,
|
|
1092
|
-
secret?: string
|
|
1093
|
-
}): Promise<
|
|
1094
|
-
SwapExecutionActionSendToAddress<true> |
|
|
1095
|
-
SwapExecutionActionWait<"LP" | "SETTLEMENT"> |
|
|
1096
|
-
SwapExecutionActionSignSmartChainTx<T> |
|
|
1097
|
-
undefined
|
|
1098
|
-
> {
|
|
1099
|
-
const executionStatus = await this._getExecutionStatus(options);
|
|
1100
|
-
return executionStatus.buildCurrentAction(options);
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
/**
|
|
1104
|
-
* @inheritDoc
|
|
1105
|
-
*/
|
|
1106
|
-
// TODO: Figure how we gonna trigger an LNURL-withdraw with the execution actions
|
|
1107
|
-
async getExecutionStatus(options?: {
|
|
1108
|
-
skipBuildingAction?: boolean,
|
|
1109
|
-
manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
|
|
1110
|
-
maxWaitTillAutomaticSettlementSeconds?: number,
|
|
1111
|
-
secret?: string
|
|
1112
|
-
}): Promise<{
|
|
1113
|
-
steps: [
|
|
1114
|
-
SwapExecutionStepPayment<"LIGHTNING">,
|
|
1115
|
-
SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
|
|
1116
|
-
],
|
|
1117
|
-
currentAction:
|
|
1118
|
-
SwapExecutionActionSendToAddress<true> |
|
|
1119
|
-
SwapExecutionActionWait<"LP" | "SETTLEMENT"> |
|
|
1120
|
-
SwapExecutionActionSignSmartChainTx<T> |
|
|
1121
|
-
undefined,
|
|
1122
|
-
stateInfo: SwapStateInfo<FromBTCLNAutoSwapState>
|
|
1123
|
-
}> {
|
|
1124
|
-
const executionStatus = await this._getExecutionStatus(options);
|
|
1125
|
-
return {
|
|
1126
|
-
steps: executionStatus.steps,
|
|
1127
|
-
currentAction: options?.skipBuildingAction ? undefined : await executionStatus.buildCurrentAction(options),
|
|
1128
|
-
stateInfo: this._getStateInfo(executionStatus.state)
|
|
1129
|
-
};
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
/**
|
|
1133
|
-
* @inheritDoc
|
|
1134
|
-
*/
|
|
1135
|
-
async getExecutionSteps(options?: {
|
|
1136
|
-
maxWaitTillAutomaticSettlementSeconds?: number
|
|
1137
|
-
}): Promise<[
|
|
1138
|
-
SwapExecutionStepPayment<"LIGHTNING">,
|
|
1139
|
-
SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
|
|
1140
|
-
]> {
|
|
1141
|
-
return (await this._getExecutionStatus(options)).steps;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
//////////////////////////////
|
|
1146
|
-
//// Payment
|
|
1147
|
-
|
|
1148
|
-
/**
|
|
1149
|
-
* Checks whether the LP received the LN payment
|
|
1150
|
-
*
|
|
1151
|
-
* @param save If the new swap state should be saved
|
|
1152
|
-
*
|
|
1153
|
-
* @internal
|
|
1154
|
-
*/
|
|
1155
|
-
async _checkIntermediaryPaymentReceived(save: boolean = true): Promise<boolean | null> {
|
|
1156
|
-
if(
|
|
1157
|
-
this._state===FromBTCLNAutoSwapState.PR_PAID ||
|
|
1158
|
-
this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED ||
|
|
1159
|
-
this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED ||
|
|
1160
|
-
this._state===FromBTCLNAutoSwapState.FAILED ||
|
|
1161
|
-
this._state===FromBTCLNAutoSwapState.EXPIRED
|
|
1162
|
-
) return true;
|
|
1163
|
-
if(this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED) return false;
|
|
1164
|
-
if(this.url==null) return false;
|
|
1165
|
-
|
|
1166
|
-
const paymentHash = this.getPaymentHash();
|
|
1167
|
-
if(paymentHash==null)
|
|
1168
|
-
throw new Error("Failed to check LP payment received, payment hash not known (probably recovered swap?)");
|
|
1169
|
-
|
|
1170
|
-
const resp = await this.wrapper._lpApi.getInvoiceStatus(this.url, paymentHash.toString("hex"));
|
|
1171
|
-
switch(resp.code) {
|
|
1172
|
-
case InvoiceStatusResponseCodes.PAID:
|
|
1173
|
-
const data = new (this.wrapper._swapDataDeserializer(this._contractVersion))(resp.data.data);
|
|
1174
|
-
if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) try {
|
|
1175
|
-
await this._saveRealSwapData(data, save);
|
|
1176
|
-
return true;
|
|
1177
|
-
} catch (e) {}
|
|
1178
|
-
return null;
|
|
1179
|
-
case InvoiceStatusResponseCodes.EXPIRED:
|
|
1180
|
-
this._state = FromBTCLNAutoSwapState.QUOTE_EXPIRED;
|
|
1181
|
-
this.initiated = true;
|
|
1182
|
-
if(save) await this._saveAndEmit();
|
|
1183
|
-
return false;
|
|
1184
|
-
default:
|
|
1185
|
-
return null;
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
/**
|
|
1190
|
-
* Checks and overrides the swap data for this swap. This is used to set the swap data from
|
|
1191
|
-
* on-chain events.
|
|
1192
|
-
*
|
|
1193
|
-
* @param data Swap data of the escrow swap
|
|
1194
|
-
* @param save If the new data should be saved
|
|
1195
|
-
*
|
|
1196
|
-
* @internal
|
|
1197
|
-
*/
|
|
1198
|
-
async _saveRealSwapData(data: T["Data"], save?: boolean): Promise<boolean> {
|
|
1199
|
-
await this.checkIntermediaryReturnedData(data);
|
|
1200
|
-
if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1201
|
-
this._state = FromBTCLNAutoSwapState.PR_PAID;
|
|
1202
|
-
this._data = data;
|
|
1203
|
-
this.initiated = true;
|
|
1204
|
-
if(save) await this._saveAndEmit();
|
|
1205
|
-
return true;
|
|
1206
|
-
}
|
|
1207
|
-
return false;
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
/**
|
|
1211
|
-
* Waits till a lightning network payment is received by the intermediary, and the intermediary
|
|
1212
|
-
* initiates the swap HTLC on the smart chain side. After the HTLC is initiated you can wait
|
|
1213
|
-
* for an automatic settlement by the watchtowers with the {@link waitTillClaimed} function,
|
|
1214
|
-
* or settle manually using the {@link claim} or {@link txsClaim} functions.
|
|
1215
|
-
*
|
|
1216
|
-
* If this swap is using an LNURL-withdraw link as input, it automatically posts the
|
|
1217
|
-
* generated invoice to the LNURL service to pay it.
|
|
1218
|
-
*
|
|
1219
|
-
* @remarks For internal use, rather use {@link waitForPayment} which properly waits till the LP also
|
|
1220
|
-
* offers a swap HTLC.
|
|
1221
|
-
*
|
|
1222
|
-
* @param abortSignal Abort signal to stop waiting for payment
|
|
1223
|
-
* @param checkIntervalSeconds How often to poll the intermediary for answer (default 5 seconds)
|
|
1224
|
-
*
|
|
1225
|
-
* @internal
|
|
1226
|
-
*/
|
|
1227
|
-
async _waitForLpPaymentReceived(checkIntervalSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
|
|
1228
|
-
checkIntervalSeconds ??= 5;
|
|
1229
|
-
if(this._state>=FromBTCLNAutoSwapState.PR_PAID) return true;
|
|
1230
|
-
if(
|
|
1231
|
-
this._state!==FromBTCLNAutoSwapState.PR_CREATED
|
|
1232
|
-
) throw new Error("Must be in PR_CREATED state!");
|
|
1233
|
-
|
|
1234
|
-
const abortController = new AbortController();
|
|
1235
|
-
if(abortSignal!=null) abortSignal.addEventListener("abort", () => abortController.abort(abortSignal.reason));
|
|
1236
|
-
|
|
1237
|
-
let save = false;
|
|
1238
|
-
|
|
1239
|
-
if(this.lnurl!=null && this.lnurlK1!=null && this.lnurlCallback!=null && !this.prPosted) {
|
|
1240
|
-
if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
|
|
1241
|
-
throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
|
|
1242
|
-
|
|
1243
|
-
LNURL.postInvoiceToLNURLWithdraw({k1: this.lnurlK1, callback: this.lnurlCallback}, this.pr).catch(e => {
|
|
1244
|
-
this.lnurlFailSignal.abort(e);
|
|
1245
|
-
});
|
|
1246
|
-
this.prPosted = true;
|
|
1247
|
-
save ||= true;
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
if(!this.initiated) {
|
|
1251
|
-
this.initiated = true;
|
|
1252
|
-
save ||= true;
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
if(save) await this._saveAndEmit();
|
|
1256
|
-
|
|
1257
|
-
let lnurlFailListener = () => abortController.abort(this.lnurlFailSignal.signal.reason);
|
|
1258
|
-
this.lnurlFailSignal.signal.addEventListener("abort", lnurlFailListener);
|
|
1259
|
-
this.lnurlFailSignal.signal.throwIfAborted();
|
|
1260
|
-
|
|
1261
|
-
const paymentHash = this.getPaymentHash();
|
|
1262
|
-
if(paymentHash==null)
|
|
1263
|
-
throw new Error("Swap payment hash not available, the swap was probably recovered!");
|
|
1264
|
-
|
|
1265
|
-
if(this.wrapper._messenger.warmup!=null) await this.wrapper._messenger.warmup().catch(e => {
|
|
1266
|
-
this.logger.warn("waitForPayment(): Failed to warmup messenger: ", e);
|
|
1267
|
-
});
|
|
1268
|
-
|
|
1269
|
-
if(this._state===FromBTCLNAutoSwapState.PR_CREATED) {
|
|
1270
|
-
const promises: Promise<boolean | undefined>[] = [
|
|
1271
|
-
this.waitTillState(FromBTCLNAutoSwapState.PR_PAID, "gte", abortController.signal).then(() => true)
|
|
1272
|
-
];
|
|
1273
|
-
if(this.url!=null) promises.push((async () => {
|
|
1274
|
-
let resp: InvoiceStatusResponse = {code: InvoiceStatusResponseCodes.PENDING, msg: ""};
|
|
1275
|
-
while(!abortController.signal.aborted && resp.code===InvoiceStatusResponseCodes.PENDING) {
|
|
1276
|
-
resp = await this.wrapper._lpApi.getInvoiceStatus(this.url!, paymentHash.toString("hex"));
|
|
1277
|
-
if(resp.code===InvoiceStatusResponseCodes.PENDING)
|
|
1278
|
-
await timeoutPromise(checkIntervalSeconds*1000, abortController.signal);
|
|
1279
|
-
}
|
|
1280
|
-
this.lnurlFailSignal.signal.removeEventListener("abort", lnurlFailListener);
|
|
1281
|
-
abortController.signal.throwIfAborted();
|
|
1282
|
-
|
|
1283
|
-
if(resp.code===InvoiceStatusResponseCodes.PAID) {
|
|
1284
|
-
const swapData = new (this.wrapper._swapDataDeserializer(this._contractVersion))(resp.data.data);
|
|
1285
|
-
return await this._saveRealSwapData(swapData, true);
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1289
|
-
if(resp.code===InvoiceStatusResponseCodes.EXPIRED) {
|
|
1290
|
-
await this._saveAndEmit(FromBTCLNAutoSwapState.QUOTE_EXPIRED);
|
|
1291
|
-
}
|
|
1292
|
-
return false;
|
|
1293
|
-
}
|
|
1294
|
-
})());
|
|
1295
|
-
const paymentResult = await Promise.race(promises);
|
|
1296
|
-
abortController.abort();
|
|
1297
|
-
|
|
1298
|
-
if(!paymentResult) return false;
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
return this._state>=FromBTCLNAutoSwapState.PR_PAID;
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
/**
|
|
1306
|
-
* Checks the data returned by the intermediary in the payment auth request
|
|
1307
|
-
*
|
|
1308
|
-
* @param data Parsed swap data as returned by the intermediary
|
|
1309
|
-
*
|
|
1310
|
-
* @throws {IntermediaryError} If the returned are not valid
|
|
1311
|
-
* @throws {Error} If the swap is already committed on-chain
|
|
1312
|
-
*
|
|
1313
|
-
* @private
|
|
1314
|
-
*/
|
|
1315
|
-
private async checkIntermediaryReturnedData(data: T["Data"]): Promise<void> {
|
|
1316
|
-
if (!data.isPayOut()) throw new IntermediaryError("Invalid not pay out");
|
|
1317
|
-
if (data.getType() !== ChainSwapType.HTLC) throw new IntermediaryError("Invalid swap type");
|
|
1318
|
-
if (!data.isOfferer(this.getSwapData().getOfferer())) throw new IntermediaryError("Invalid offerer used");
|
|
1319
|
-
if (!data.isClaimer(this._getInitiator())) throw new IntermediaryError("Invalid claimer used");
|
|
1320
|
-
if (!data.isToken(this.getSwapData().getToken())) throw new IntermediaryError("Invalid token used");
|
|
1321
|
-
if (data.getSecurityDeposit() !== this.getSwapData().getSecurityDeposit()) throw new IntermediaryError("Invalid security deposit!");
|
|
1322
|
-
if (data.getClaimerBounty() !== this.getSwapData().getClaimerBounty()) throw new IntermediaryError("Invalid security deposit!");
|
|
1323
|
-
if (data.getAmount() < this.getSwapData().getAmount()) throw new IntermediaryError("Invalid amount received!");
|
|
1324
|
-
if (data.getClaimHash() !== this.getSwapData().getClaimHash()) throw new IntermediaryError("Invalid payment hash used!");
|
|
1325
|
-
if (!data.isDepositToken(this.getSwapData().getDepositToken())) throw new IntermediaryError("Invalid deposit token used!");
|
|
1326
|
-
if (data.hasSuccessAction()) throw new IntermediaryError("Invalid has success action");
|
|
1327
|
-
|
|
1328
|
-
if (await this.wrapper._contract(this._contractVersion).isExpired(this._getInitiator(), data)) throw new IntermediaryError("Not enough time to claim!");
|
|
1329
|
-
if (this.wrapper._getHtlcTimeout(data) <= (Date.now()/1000)) throw new IntermediaryError("HTLC expires too soon!");
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
/**
|
|
1333
|
-
* Waits till a lightning network payment is received by the intermediary, and the intermediary
|
|
1334
|
-
* initiates the swap HTLC on the smart chain side. After the HTLC is initiated you can wait
|
|
1335
|
-
* for an automatic settlement by the watchtowers with the {@link waitTillClaimed} function,
|
|
1336
|
-
* or settle manually using the {@link claim} or {@link txsClaim} functions.
|
|
1337
|
-
*
|
|
1338
|
-
* If this swap is using an LNURL-withdraw link as input, it automatically posts the
|
|
1339
|
-
* generated invoice to the LNURL service to pay it.
|
|
1340
|
-
*
|
|
1341
|
-
* @param onPaymentReceived Callback as for when the LP reports having received the ln payment
|
|
1342
|
-
* @param abortSignal Abort signal to stop waiting for payment
|
|
1343
|
-
* @param checkIntervalSeconds How often to poll the intermediary for answer (default 5 seconds)
|
|
1344
|
-
*/
|
|
1345
|
-
async waitForPayment(onPaymentReceived?: (txId: string) => void, checkIntervalSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
|
|
1346
|
-
checkIntervalSeconds ??= 5;
|
|
1347
|
-
if(this._state===FromBTCLNAutoSwapState.PR_PAID) {
|
|
1348
|
-
await this.waitTillCommited(checkIntervalSeconds, abortSignal);
|
|
1349
|
-
}
|
|
1350
|
-
if(this._state>=FromBTCLNAutoSwapState.CLAIM_COMMITED) return true;
|
|
1351
|
-
|
|
1352
|
-
const success = await this._waitForLpPaymentReceived(checkIntervalSeconds, abortSignal);
|
|
1353
|
-
if(!success) return false;
|
|
1354
|
-
|
|
1355
|
-
if(onPaymentReceived!=null) onPaymentReceived(this.getInputTxId()!);
|
|
1356
|
-
|
|
1357
|
-
if((this._state as FromBTCLNAutoSwapState)===FromBTCLNAutoSwapState.PR_PAID) {
|
|
1358
|
-
await this.waitTillCommited(checkIntervalSeconds, abortSignal);
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
return this._state>=FromBTCLNAutoSwapState.CLAIM_COMMITED;
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
//////////////////////////////
|
|
1366
|
-
//// Commit
|
|
1367
|
-
|
|
1368
|
-
/**
|
|
1369
|
-
* Waits till the intermediary (LP) initiates the swap HTLC escrow on the destination smart chain side
|
|
1370
|
-
*
|
|
1371
|
-
* @param checkIntervalSeconds How often to check via a polling watchdog
|
|
1372
|
-
* @param abortSignal Abort signal
|
|
1373
|
-
*
|
|
1374
|
-
* @internal
|
|
1375
|
-
*/
|
|
1376
|
-
protected async waitTillCommited(checkIntervalSeconds?: number, abortSignal?: AbortSignal): Promise<void> {
|
|
1377
|
-
if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED || this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED) return Promise.resolve();
|
|
1378
|
-
if(this._state!==FromBTCLNAutoSwapState.PR_PAID) throw new Error("Invalid state");
|
|
1379
|
-
|
|
1380
|
-
const abortController = extendAbortController(abortSignal);
|
|
1381
|
-
let result: SwapCommitState | number | null;
|
|
1382
|
-
try {
|
|
1383
|
-
result = await Promise.race([
|
|
1384
|
-
this.watchdogWaitTillCommited(checkIntervalSeconds, abortController.signal),
|
|
1385
|
-
this.waitTillState(FromBTCLNAutoSwapState.CLAIM_COMMITED, "gte", abortController.signal).then(() => 0)
|
|
1386
|
-
]);
|
|
1387
|
-
abortController.abort();
|
|
1388
|
-
} catch (e) {
|
|
1389
|
-
abortController.abort();
|
|
1390
|
-
throw e;
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
if(result===null) {
|
|
1394
|
-
this.logger.debug("waitTillCommited(): Resolved from watchdog - HTLC expired");
|
|
1395
|
-
if(
|
|
1396
|
-
this._state===FromBTCLNAutoSwapState.PR_PAID
|
|
1397
|
-
) {
|
|
1398
|
-
await this._saveAndEmit(FromBTCLNAutoSwapState.EXPIRED);
|
|
1399
|
-
}
|
|
1400
|
-
return;
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
if(
|
|
1404
|
-
this._state===FromBTCLNAutoSwapState.PR_PAID
|
|
1405
|
-
) {
|
|
1406
|
-
if(typeof(result)==="object" && (result as any).getInitTxId!=null && this._commitTxId==null)
|
|
1407
|
-
this._commitTxId = await (result as any).getInitTxId();
|
|
1408
|
-
this._commitedAt ??= Date.now();
|
|
1409
|
-
await this._saveAndEmit(FromBTCLNAutoSwapState.CLAIM_COMMITED);
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
if(result===0) {
|
|
1413
|
-
this.logger.debug("waitTillCommited(): Resolved from state changed");
|
|
1414
|
-
} else if(result!=null) {
|
|
1415
|
-
this.logger.debug("waitTillCommited(): Resolved from watchdog - commited");
|
|
1416
|
-
if(this.secret!=null) await this._broadcastSecret().catch(e => {
|
|
1417
|
-
this.logger.error("waitTillCommited(): Error broadcasting swap secret: ", e);
|
|
1418
|
-
});
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
//////////////////////////////
|
|
1424
|
-
//// Claim
|
|
1425
|
-
|
|
1426
|
-
/**
|
|
1427
|
-
* @inheritDoc
|
|
1428
|
-
*
|
|
1429
|
-
* @param _signer Optional signer address to use for claiming the swap, can also be different from the initializer
|
|
1430
|
-
* @param secret A swap secret to use for the claim transaction, generally only needed if the swap
|
|
1431
|
-
* was recovered from on-chain data, or the pre-image was generated outside the SDK
|
|
1432
|
-
*
|
|
1433
|
-
* @throws {Error} If in invalid state (must be {@link FromBTCLNAutoSwapState.CLAIM_COMMITED})
|
|
1434
|
-
*/
|
|
1435
|
-
async txsClaim(_signer?: string | T["Signer"] | T["NativeSigner"], secret?: string): Promise<T["TX"][]> {
|
|
1436
|
-
let address: string | undefined = undefined;
|
|
1437
|
-
if(_signer!=null) {
|
|
1438
|
-
if (typeof (_signer) === "string") {
|
|
1439
|
-
address = _signer;
|
|
1440
|
-
} else if (isAbstractSigner(_signer)) {
|
|
1441
|
-
address = _signer.getAddress();
|
|
1442
|
-
} else {
|
|
1443
|
-
address = (await this.wrapper._chain.wrapSigner(_signer)).getAddress();
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
if(this._state!==FromBTCLNAutoSwapState.CLAIM_COMMITED) throw new Error("Must be in CLAIM_COMMITED state!");
|
|
1447
|
-
if(this._data==null) throw new Error("Unknown data, wrong state?");
|
|
1448
|
-
|
|
1449
|
-
const useSecret = secret ?? this.secret;
|
|
1450
|
-
if(useSecret==null)
|
|
1451
|
-
throw new Error("Swap secret pre-image not known and not provided, please provide the swap secret pre-image as an argument");
|
|
1452
|
-
if(!this.isValidSecretPreimage(useSecret))
|
|
1453
|
-
throw new Error("Invalid swap secret pre-image provided!");
|
|
1454
|
-
|
|
1455
|
-
return await this._contract.txsClaimWithSecret(
|
|
1456
|
-
address ?? this._getInitiator(),
|
|
1457
|
-
this._data, useSecret, true, true
|
|
1458
|
-
);
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
/**
|
|
1462
|
-
* @inheritDoc
|
|
1463
|
-
*
|
|
1464
|
-
* @param _signer Signer to sign the transactions with, can also be different to the initializer
|
|
1465
|
-
* @param abortSignal Abort signal to stop waiting for transaction confirmation
|
|
1466
|
-
* @param onBeforeTxSent
|
|
1467
|
-
* @param secret A swap secret to use for the claim transaction, generally only needed if the swap
|
|
1468
|
-
* was recovered from on-chain data, or the pre-image was generated outside the SDK
|
|
1469
|
-
*/
|
|
1470
|
-
async claim(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, onBeforeTxSent?: (txId: string) => void, secret?: string): Promise<string> {
|
|
1471
|
-
const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
|
|
1472
|
-
let txCount = 0;
|
|
1473
|
-
const txs = await this.txsClaim(_signer, secret);
|
|
1474
|
-
const result = await this.wrapper._chain.sendAndConfirm(
|
|
1475
|
-
signer, txs, true, abortSignal, undefined, (txId: string) => {
|
|
1476
|
-
txCount++;
|
|
1477
|
-
if(onBeforeTxSent!=null && txCount===1) onBeforeTxSent(txId);
|
|
1478
|
-
return Promise.resolve();
|
|
1479
|
-
}
|
|
1480
|
-
);
|
|
1481
|
-
|
|
1482
|
-
this._claimTxId = result[0];
|
|
1483
|
-
if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED || this._state===FromBTCLNAutoSwapState.EXPIRED || this._state===FromBTCLNAutoSwapState.FAILED) {
|
|
1484
|
-
await this._saveAndEmit(FromBTCLNAutoSwapState.CLAIM_CLAIMED);
|
|
1485
|
-
}
|
|
1486
|
-
return result[0];
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
/**
|
|
1490
|
-
* Waits till the swap is successfully settled (claimed), should be called after sending the claim (settlement)
|
|
1491
|
-
* transactions manually to wait till the SDK processes the settlement and updates the swap state accordingly.
|
|
1492
|
-
*
|
|
1493
|
-
* @param maxWaitTimeSeconds Maximum time in seconds to wait for the swap to be settled
|
|
1494
|
-
* @param abortSignal AbortSignal
|
|
1495
|
-
* @param secret A swap secret to broadcast to watchtowers, generally only needed if the swap
|
|
1496
|
-
* was recovered from on-chain data, or the pre-image was generated outside the SDK
|
|
1497
|
-
* @param pollIntervalSeconds How often to poll via the watchdog
|
|
1498
|
-
*
|
|
1499
|
-
* @throws {Error} If swap is in invalid state (must be {@link FromBTCLNAutoSwapState.CLAIM_COMMITED})
|
|
1500
|
-
* @throws {Error} If the LP refunded sooner than we were able to claim
|
|
1501
|
-
* @returns {boolean} whether the swap was claimed in time or not
|
|
1502
|
-
*/
|
|
1503
|
-
async waitTillClaimed(maxWaitTimeSeconds?: number, abortSignal?: AbortSignal, secret?: string, pollIntervalSeconds?: number): Promise<boolean> {
|
|
1504
|
-
if(this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED) return Promise.resolve(true);
|
|
1505
|
-
if(this._state!==FromBTCLNAutoSwapState.CLAIM_COMMITED) throw new Error("Invalid state (not CLAIM_COMMITED)");
|
|
1506
|
-
|
|
1507
|
-
if(secret!=null) {
|
|
1508
|
-
if(!this.isValidSecretPreimage(secret))
|
|
1509
|
-
throw new Error("Invalid swap secret pre-image provided!");
|
|
1510
|
-
this.secret = secret;
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
const abortController = new AbortController();
|
|
1514
|
-
if(abortSignal!=null) abortSignal.addEventListener("abort", () => abortController.abort(abortSignal.reason));
|
|
1515
|
-
let timedOut: boolean = false;
|
|
1516
|
-
if(maxWaitTimeSeconds!=null) {
|
|
1517
|
-
const timeout = setTimeout(() => {
|
|
1518
|
-
timedOut = true;
|
|
1519
|
-
abortController.abort();
|
|
1520
|
-
}, maxWaitTimeSeconds * 1000);
|
|
1521
|
-
abortController.signal.addEventListener("abort", () => clearTimeout(timeout));
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
let res: 0 | 1 | SwapCommitState;
|
|
1525
|
-
try {
|
|
1526
|
-
res = await Promise.race([
|
|
1527
|
-
this.watchdogWaitTillResult(pollIntervalSeconds, abortController.signal),
|
|
1528
|
-
this.waitTillState(FromBTCLNAutoSwapState.CLAIM_CLAIMED, "eq", abortController.signal).then(() => 0 as const),
|
|
1529
|
-
this.waitTillState(FromBTCLNAutoSwapState.EXPIRED, "eq", abortController.signal).then(() => 1 as const),
|
|
1530
|
-
]);
|
|
1531
|
-
abortController.abort();
|
|
1532
|
-
} catch (e) {
|
|
1533
|
-
abortController.abort();
|
|
1534
|
-
if(timedOut) return false;
|
|
1535
|
-
throw e;
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
if(res===0) {
|
|
1539
|
-
this.logger.debug("waitTillClaimed(): Resolved from state change (CLAIM_CLAIMED)");
|
|
1540
|
-
return true;
|
|
1541
|
-
}
|
|
1542
|
-
if(res===1) {
|
|
1543
|
-
this.logger.debug("waitTillClaimed(): Resolved from state change (EXPIRED)");
|
|
1544
|
-
throw new Error("Swap expired during claiming");
|
|
1545
|
-
}
|
|
1546
|
-
this.logger.debug("waitTillClaimed(): Resolved from watchdog");
|
|
1547
|
-
|
|
1548
|
-
if(res?.type===SwapCommitStateType.PAID) {
|
|
1549
|
-
if((this._state as FromBTCLNAutoSwapState)!==FromBTCLNAutoSwapState.CLAIM_CLAIMED) {
|
|
1550
|
-
this._claimTxId = await res.getClaimTxId();
|
|
1551
|
-
await this._saveAndEmit(FromBTCLNAutoSwapState.CLAIM_CLAIMED);
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
if(res?.type===SwapCommitStateType.NOT_COMMITED || res?.type===SwapCommitStateType.EXPIRED) {
|
|
1555
|
-
if(
|
|
1556
|
-
(this._state as FromBTCLNAutoSwapState)!==FromBTCLNAutoSwapState.CLAIM_CLAIMED &&
|
|
1557
|
-
(this._state as FromBTCLNAutoSwapState)!==FromBTCLNAutoSwapState.FAILED
|
|
1558
|
-
) {
|
|
1559
|
-
await this._saveAndEmit(FromBTCLNAutoSwapState.FAILED);
|
|
1560
|
-
}
|
|
1561
|
-
throw new Error("Swap expired during claiming");
|
|
1562
|
-
}
|
|
1563
|
-
return true;
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
//////////////////////////////
|
|
1568
|
-
//// LNURL
|
|
1569
|
-
|
|
1570
|
-
/**
|
|
1571
|
-
* Whether this swap uses an LNURL-withdraw link
|
|
1572
|
-
*/
|
|
1573
|
-
isLNURL(): boolean {
|
|
1574
|
-
return this.lnurl!=null;
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
/**
|
|
1578
|
-
* Gets the used LNURL or `null` if this is not an LNURL-withdraw swap
|
|
1579
|
-
*/
|
|
1580
|
-
getLNURL(): string | null {
|
|
1581
|
-
return this.lnurl ?? null;
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
/**
|
|
1585
|
-
* Pay the generated lightning network invoice with an LNURL-withdraw link, this
|
|
1586
|
-
* is useful when you want to display a lightning payment QR code and also want to
|
|
1587
|
-
* allow payments using LNURL-withdraw NFC cards.
|
|
1588
|
-
*
|
|
1589
|
-
* Note that the swap needs to be created **without** an LNURL to begin with for this function
|
|
1590
|
-
* to work. If this swap is already using an LNURL-withdraw link, this function throws.
|
|
1591
|
-
*/
|
|
1592
|
-
async settleWithLNURLWithdraw(lnurl: string | LNURLWithdraw): Promise<void> {
|
|
1593
|
-
if(
|
|
1594
|
-
this._state!==FromBTCLNAutoSwapState.PR_CREATED &&
|
|
1595
|
-
this._state!==FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED
|
|
1596
|
-
) throw new Error("Must be in PR_CREATED state!");
|
|
1597
|
-
|
|
1598
|
-
if(this.lnurl!=null) throw new Error("Cannot settle LNURL-withdraw swap with different LNURL");
|
|
1599
|
-
let lnurlParams: LNURLWithdrawParamsWithUrl;
|
|
1600
|
-
if(typeof(lnurl)==="string") {
|
|
1601
|
-
const parsedLNURL = await LNURL.getLNURL(lnurl);
|
|
1602
|
-
if(parsedLNURL==null || parsedLNURL.tag!=="withdrawRequest")
|
|
1603
|
-
throw new UserError("Invalid LNURL-withdraw to settle the swap");
|
|
1604
|
-
lnurlParams = parsedLNURL;
|
|
1605
|
-
} else {
|
|
1606
|
-
lnurlParams = lnurl.params;
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
|
|
1610
|
-
throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
|
|
1611
|
-
|
|
1612
|
-
LNURL.useLNURLWithdraw(lnurlParams, this.pr).catch(e => this.lnurlFailSignal.abort(e));
|
|
1613
|
-
this.lnurl = lnurlParams.url;
|
|
1614
|
-
this.lnurlCallback = lnurlParams.callback;
|
|
1615
|
-
this.lnurlK1 = lnurlParams.k1;
|
|
1616
|
-
this.prPosted = true;
|
|
1617
|
-
await this._saveAndEmit();
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
//////////////////////////////
|
|
1622
|
-
//// Storage
|
|
1623
|
-
|
|
1624
|
-
/**
|
|
1625
|
-
* @inheritDoc
|
|
1626
|
-
*/
|
|
1627
|
-
serialize(): any {
|
|
1628
|
-
return {
|
|
1629
|
-
...super.serialize(),
|
|
1630
|
-
data: this._data==null ? null : this._data.serialize(),
|
|
1631
|
-
commitTxId: this._commitTxId,
|
|
1632
|
-
claimTxId: this._claimTxId,
|
|
1633
|
-
commitedAt: this._commitedAt,
|
|
1634
|
-
btcAmountSwap: this.btcAmountSwap==null ? null : this.btcAmountSwap.toString(10),
|
|
1635
|
-
btcAmountGas: this.btcAmountGas==null ? null : this.btcAmountGas.toString(10),
|
|
1636
|
-
gasSwapFeeBtc: this.gasSwapFeeBtc==null ? null : this.gasSwapFeeBtc.toString(10),
|
|
1637
|
-
gasSwapFee: this.gasSwapFee==null ? null : this.gasSwapFee.toString(10),
|
|
1638
|
-
gasPricingInfo: serializePriceInfoType(this.gasPricingInfo),
|
|
1639
|
-
pr: this.pr,
|
|
1640
|
-
secret: this.secret,
|
|
1641
|
-
lnurl: this.lnurl,
|
|
1642
|
-
lnurlK1: this.lnurlK1,
|
|
1643
|
-
lnurlCallback: this.lnurlCallback,
|
|
1644
|
-
prPosted: this.prPosted,
|
|
1645
|
-
initialSwapData: this.initialSwapData.serialize(),
|
|
1646
|
-
usesClaimHashAsId: this.usesClaimHashAsId
|
|
1647
|
-
};
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
//////////////////////////////
|
|
1652
|
-
//// Swap ticks & sync
|
|
1653
|
-
|
|
1654
|
-
/**
|
|
1655
|
-
* Checks the swap's state on-chain and compares it to its internal state, updates/changes it according to on-chain
|
|
1656
|
-
* data
|
|
1657
|
-
*
|
|
1658
|
-
* @private
|
|
1659
|
-
*/
|
|
1660
|
-
private async syncStateFromChain(quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState): Promise<boolean> {
|
|
1661
|
-
if(
|
|
1662
|
-
this._state===FromBTCLNAutoSwapState.PR_PAID ||
|
|
1663
|
-
this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED ||
|
|
1664
|
-
this._state===FromBTCLNAutoSwapState.EXPIRED
|
|
1665
|
-
) {
|
|
1666
|
-
//Check for expiry before the getCommitStatus to prevent race conditions
|
|
1667
|
-
let quoteExpired: boolean = false;
|
|
1668
|
-
if(this._state===FromBTCLNAutoSwapState.PR_PAID) {
|
|
1669
|
-
quoteExpired = quoteDefinitelyExpired ?? await this._verifyQuoteDefinitelyExpired();
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
//Check if it's already successfully paid
|
|
1673
|
-
commitStatus ??= await this._contract.getCommitStatus(this._getInitiator(), this._data!);
|
|
1674
|
-
if(commitStatus!=null && await this._forciblySetOnchainState(commitStatus)) return true;
|
|
1675
|
-
|
|
1676
|
-
if(this._state===FromBTCLNAutoSwapState.PR_PAID) {
|
|
1677
|
-
if(quoteExpired) {
|
|
1678
|
-
this._state = FromBTCLNAutoSwapState.QUOTE_EXPIRED;
|
|
1679
|
-
return true;
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
return false;
|
|
1685
|
-
}
|
|
1686
|
-
|
|
1687
|
-
/**
|
|
1688
|
-
* @inheritDoc
|
|
1689
|
-
* @internal
|
|
1690
|
-
*/
|
|
1691
|
-
_shouldFetchOnchainState(): boolean {
|
|
1692
|
-
return this._state===FromBTCLNAutoSwapState.PR_PAID || this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED || this._state===FromBTCLNAutoSwapState.EXPIRED;
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
/**
|
|
1696
|
-
* @inheritDoc
|
|
1697
|
-
* @internal
|
|
1698
|
-
*/
|
|
1699
|
-
_shouldFetchExpiryStatus(): boolean {
|
|
1700
|
-
return this._state===FromBTCLNAutoSwapState.PR_PAID;
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
/**
|
|
1704
|
-
* @inheritDoc
|
|
1705
|
-
* @internal
|
|
1706
|
-
*/
|
|
1707
|
-
_shouldCheckIntermediary(): boolean {
|
|
1708
|
-
return this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED;
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
/**
|
|
1712
|
-
* @inheritDoc
|
|
1713
|
-
* @internal
|
|
1714
|
-
*/
|
|
1715
|
-
async _sync(save?: boolean, quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState, skipLpCheck?: boolean): Promise<boolean> {
|
|
1716
|
-
let changed = false;
|
|
1717
|
-
|
|
1718
|
-
if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1719
|
-
if(this._state!==FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED && this.getQuoteExpiry()<Date.now()) {
|
|
1720
|
-
this._state = FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED;
|
|
1721
|
-
changed ||= true;
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
if(!skipLpCheck) try {
|
|
1725
|
-
const result = await this._checkIntermediaryPaymentReceived(false);
|
|
1726
|
-
if (result !== null) changed ||= true;
|
|
1727
|
-
} catch(e) {
|
|
1728
|
-
this.logger.error("_sync(): Failed to synchronize swap, error: ", e);
|
|
1729
|
-
}
|
|
1730
|
-
|
|
1731
|
-
if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1732
|
-
if(await this._verifyQuoteDefinitelyExpired()) {
|
|
1733
|
-
this._state = FromBTCLNAutoSwapState.QUOTE_EXPIRED;
|
|
1734
|
-
changed ||= true;
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
if(await this.syncStateFromChain(quoteDefinitelyExpired, commitStatus)) changed = true;
|
|
1740
|
-
|
|
1741
|
-
if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED) {
|
|
1742
|
-
const expired = await this._contract.isExpired(this._getInitiator(), this._data!);
|
|
1743
|
-
if(expired) {
|
|
1744
|
-
this._state = FromBTCLNAutoSwapState.EXPIRED;
|
|
1745
|
-
changed = true;
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
if(save && changed) await this._saveAndEmit();
|
|
1750
|
-
|
|
1751
|
-
if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED && this.secret!=null) await this._broadcastSecret().catch(e => {
|
|
1752
|
-
this.logger.error("_sync(): Error when broadcasting swap secret: ", e);
|
|
1753
|
-
});
|
|
1754
|
-
|
|
1755
|
-
return changed;
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
/**
|
|
1759
|
-
* @inheritDoc
|
|
1760
|
-
* @internal
|
|
1761
|
-
*/
|
|
1762
|
-
async _forciblySetOnchainState(commitStatus: SwapCommitState): Promise<boolean> {
|
|
1763
|
-
switch(commitStatus?.type) {
|
|
1764
|
-
case SwapCommitStateType.PAID:
|
|
1765
|
-
if(this._commitTxId==null && (commitStatus as any).getInitTxId!=null) this._commitTxId = await (commitStatus as any).getInitTxId();
|
|
1766
|
-
if(this._claimTxId==null) this._claimTxId = await commitStatus.getClaimTxId();
|
|
1767
|
-
if(this.secret==null || this.pr==null) this._setSwapSecret(await commitStatus.getClaimResult());
|
|
1768
|
-
this._state = FromBTCLNAutoSwapState.CLAIM_CLAIMED;
|
|
1769
|
-
return true;
|
|
1770
|
-
case SwapCommitStateType.NOT_COMMITED:
|
|
1771
|
-
let changed: boolean = false;
|
|
1772
|
-
if(this._commitTxId==null && (commitStatus as any).getInitTxId!=null) {
|
|
1773
|
-
this._commitTxId = await (commitStatus as any).getInitTxId();
|
|
1774
|
-
changed = true;
|
|
1775
|
-
}
|
|
1776
|
-
if(this._refundTxId==null && commitStatus.getRefundTxId!=null) {
|
|
1777
|
-
this._refundTxId = await commitStatus.getRefundTxId();
|
|
1778
|
-
changed = true;
|
|
1779
|
-
}
|
|
1780
|
-
if(this._refundTxId!=null) {
|
|
1781
|
-
this._state = FromBTCLNAutoSwapState.FAILED;
|
|
1782
|
-
changed = true;
|
|
1783
|
-
}
|
|
1784
|
-
return changed;
|
|
1785
|
-
case SwapCommitStateType.EXPIRED:
|
|
1786
|
-
if(this._commitTxId==null && (commitStatus as any).getInitTxId!=null) this._commitTxId = await (commitStatus as any).getInitTxId();
|
|
1787
|
-
if(this._refundTxId==null && commitStatus.getRefundTxId!=null) this._refundTxId = await commitStatus.getRefundTxId();
|
|
1788
|
-
this._state = this._refundTxId==null ? FromBTCLNAutoSwapState.QUOTE_EXPIRED : FromBTCLNAutoSwapState.FAILED;
|
|
1789
|
-
return true;
|
|
1790
|
-
case SwapCommitStateType.COMMITED:
|
|
1791
|
-
let save: boolean = false;
|
|
1792
|
-
if(this._commitTxId==null && (commitStatus as any).getInitTxId!=null) {
|
|
1793
|
-
this._commitTxId = await (commitStatus as any).getInitTxId();
|
|
1794
|
-
save = true;
|
|
1795
|
-
}
|
|
1796
|
-
if(this._state!==FromBTCLNAutoSwapState.CLAIM_COMMITED && this._state!==FromBTCLNAutoSwapState.EXPIRED) {
|
|
1797
|
-
this._commitedAt ??= Date.now();
|
|
1798
|
-
this._state = FromBTCLNAutoSwapState.CLAIM_COMMITED;
|
|
1799
|
-
save = true;
|
|
1800
|
-
}
|
|
1801
|
-
return save;
|
|
1802
|
-
}
|
|
1803
|
-
return false;
|
|
1804
|
-
}
|
|
1805
|
-
|
|
1806
|
-
/**
|
|
1807
|
-
* Broadcasts the swap secret to the underlying data propagation layer (e.g. Nostr by default)
|
|
1808
|
-
*
|
|
1809
|
-
* @param noCheckExpiry Whether a swap expiration check should be skipped broadcasting
|
|
1810
|
-
* @param secret An optional secret pre-image for the swap to broadcast
|
|
1811
|
-
*
|
|
1812
|
-
* @internal
|
|
1813
|
-
*/
|
|
1814
|
-
async _broadcastSecret(noCheckExpiry?: boolean, secret?: string): Promise<void> {
|
|
1815
|
-
if(this._state!==FromBTCLNAutoSwapState.CLAIM_COMMITED) throw new Error("Must be in CLAIM_COMMITED state to broadcast swap secret!");
|
|
1816
|
-
if(this._data==null) throw new Error("Unknown data, wrong state?");
|
|
1817
|
-
|
|
1818
|
-
const useSecret = secret ?? this.secret;
|
|
1819
|
-
if(useSecret==null)
|
|
1820
|
-
throw new Error("Swap secret pre-image not known and not provided, please provide the swap secret pre-image as an argument");
|
|
1821
|
-
if(!this.isValidSecretPreimage(useSecret))
|
|
1822
|
-
throw new Error("Invalid swap secret pre-image provided!");
|
|
1823
|
-
|
|
1824
|
-
if(!noCheckExpiry) {
|
|
1825
|
-
if(await this._contract.isExpired(this._getInitiator(), this._data)) throw new Error("On-chain HTLC already expired!");
|
|
1826
|
-
}
|
|
1827
|
-
await this.wrapper._messenger.broadcast(new SwapClaimWitnessMessage(this._data, useSecret));
|
|
1828
|
-
}
|
|
1829
|
-
|
|
1830
|
-
/**
|
|
1831
|
-
* @inheritDoc
|
|
1832
|
-
* @internal
|
|
1833
|
-
*/
|
|
1834
|
-
async _tick(save?: boolean): Promise<boolean> {
|
|
1835
|
-
switch(this._state) {
|
|
1836
|
-
case FromBTCLNAutoSwapState.PR_CREATED:
|
|
1837
|
-
if(this.getQuoteExpiry() < Date.now()) {
|
|
1838
|
-
this._state = FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED;
|
|
1839
|
-
if(save) await this._saveAndEmit();
|
|
1840
|
-
return true;
|
|
1841
|
-
}
|
|
1842
|
-
break;
|
|
1843
|
-
case FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED:
|
|
1844
|
-
if(this.getDefinitiveExpiryTime() < Date.now()) {
|
|
1845
|
-
this._state = FromBTCLNAutoSwapState.QUOTE_EXPIRED;
|
|
1846
|
-
if(save) await this._saveAndEmit();
|
|
1847
|
-
return true;
|
|
1848
|
-
}
|
|
1849
|
-
break;
|
|
1850
|
-
case FromBTCLNAutoSwapState.PR_PAID:
|
|
1851
|
-
case FromBTCLNAutoSwapState.CLAIM_COMMITED:
|
|
1852
|
-
const expired = await this._contract.isExpired(this._getInitiator(), this._data!);
|
|
1853
|
-
if(expired) {
|
|
1854
|
-
this._state = FromBTCLNAutoSwapState.EXPIRED;
|
|
1855
|
-
if(save) await this._saveAndEmit();
|
|
1856
|
-
return true;
|
|
1857
|
-
}
|
|
1858
|
-
if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED) {
|
|
1859
|
-
//Broadcast the secret over the provided messenger channel
|
|
1860
|
-
if(this.broadcastTickCounter===0 && this.secret!=null) await this._broadcastSecret(true).catch(e => {
|
|
1861
|
-
this.logger.warn("_tick(): Error when broadcasting swap secret: ", e);
|
|
1862
|
-
});
|
|
1863
|
-
this.broadcastTickCounter = (this.broadcastTickCounter + 1) % 3; //Broadcast every 3rd tick
|
|
1864
|
-
}
|
|
1865
|
-
break;
|
|
1866
|
-
}
|
|
1867
|
-
|
|
1868
|
-
return false;
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
|
-
/**
|
|
1872
|
-
* Forcibly sets the swap secret pre-image from on-chain data
|
|
1873
|
-
*
|
|
1874
|
-
* @internal
|
|
1875
|
-
*/
|
|
1876
|
-
_setSwapSecret(secret: string) {
|
|
1877
|
-
this.secret = secret;
|
|
1878
|
-
if(this.pr==null) {
|
|
1879
|
-
this.pr = Buffer.from(sha256(Buffer.from(secret, "hex"))).toString("hex");
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
|
|
1883
|
-
}
|
|
1
|
+
import {decode as bolt11Decode} from "@atomiqlabs/bolt11";
|
|
2
|
+
import {SwapType} from "../../../../enums/SwapType";
|
|
3
|
+
import {
|
|
4
|
+
ChainSwapType,
|
|
5
|
+
ChainType,
|
|
6
|
+
isAbstractSigner,
|
|
7
|
+
SwapClaimWitnessMessage,
|
|
8
|
+
SwapCommitState,
|
|
9
|
+
SwapCommitStateType,
|
|
10
|
+
SwapData,
|
|
11
|
+
} from "@atomiqlabs/base";
|
|
12
|
+
import {Buffer} from "buffer";
|
|
13
|
+
import {LNURL} from "../../../../lnurl/LNURL";
|
|
14
|
+
import {UserError} from "../../../../errors/UserError";
|
|
15
|
+
import {
|
|
16
|
+
IntermediaryAPI,
|
|
17
|
+
InvoiceStatusResponse,
|
|
18
|
+
InvoiceStatusResponseCodes
|
|
19
|
+
} from "../../../../intermediaries/apis/IntermediaryAPI";
|
|
20
|
+
import {IntermediaryError} from "../../../../errors/IntermediaryError";
|
|
21
|
+
import {extendAbortController, toBigInt} from "../../../../utils/Utils";
|
|
22
|
+
import {Fee} from "../../../../types/fees/Fee";
|
|
23
|
+
import {IAddressSwap} from "../../../IAddressSwap";
|
|
24
|
+
import {FromBTCLNAutoDefinition, FromBTCLNAutoWrapper} from "./FromBTCLNAutoWrapper";
|
|
25
|
+
import {ISwapWithGasDrop} from "../../../ISwapWithGasDrop";
|
|
26
|
+
import {MinimalLightningNetworkWalletInterface} from "../../../../types/wallets/MinimalLightningNetworkWalletInterface";
|
|
27
|
+
import {IClaimableSwap} from "../../../IClaimableSwap";
|
|
28
|
+
import {IEscrowSwap, IEscrowSwapInit, isIEscrowSwapInit} from "../../IEscrowSwap";
|
|
29
|
+
import {FeeType} from "../../../../enums/FeeType";
|
|
30
|
+
import {ppmToPercentage} from "../../../../types/fees/PercentagePPM";
|
|
31
|
+
import {TokenAmount, toTokenAmount} from "../../../../types/TokenAmount";
|
|
32
|
+
import {BitcoinTokens, BtcToken, SCToken} from "../../../../types/Token";
|
|
33
|
+
import {getLogger, LoggerType} from "../../../../utils/Logger";
|
|
34
|
+
import {timeoutPromise} from "../../../../utils/TimeoutUtils";
|
|
35
|
+
import {isLNURLWithdraw, LNURLWithdraw, LNURLWithdrawParamsWithUrl} from "../../../../types/lnurl/LNURLWithdraw";
|
|
36
|
+
import {
|
|
37
|
+
deserializePriceInfoType,
|
|
38
|
+
isPriceInfoType,
|
|
39
|
+
PriceInfoType,
|
|
40
|
+
serializePriceInfoType
|
|
41
|
+
} from "../../../../types/PriceInfoType";
|
|
42
|
+
import {sha256} from "@noble/hashes/sha2";
|
|
43
|
+
import {
|
|
44
|
+
SwapExecutionActionSendToAddress,
|
|
45
|
+
SwapExecutionActionSignSmartChainTx,
|
|
46
|
+
SwapExecutionActionWait
|
|
47
|
+
} from "../../../../types/SwapExecutionAction";
|
|
48
|
+
import {
|
|
49
|
+
SwapExecutionStepPayment,
|
|
50
|
+
SwapExecutionStepSettlement
|
|
51
|
+
} from "../../../../types/SwapExecutionStep";
|
|
52
|
+
import {SwapStateInfo} from "../../../../types/SwapStateInfo";
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* State enum for FromBTCLNAuto swaps
|
|
56
|
+
* @category Swaps/Lightning → Smart chain
|
|
57
|
+
*/
|
|
58
|
+
export enum FromBTCLNAutoSwapState {
|
|
59
|
+
/**
|
|
60
|
+
* Swap has failed as the user didn't settle the HTLC on the destination before expiration
|
|
61
|
+
*/
|
|
62
|
+
FAILED = -4,
|
|
63
|
+
/**
|
|
64
|
+
* Swap has expired for good and there is no way how it can be executed anymore
|
|
65
|
+
*/
|
|
66
|
+
QUOTE_EXPIRED = -3,
|
|
67
|
+
/**
|
|
68
|
+
* A swap is almost expired, and it should be presented to the user as expired, though
|
|
69
|
+
* there is still a chance that it will be processed
|
|
70
|
+
*/
|
|
71
|
+
QUOTE_SOFT_EXPIRED = -2,
|
|
72
|
+
/**
|
|
73
|
+
* Swap HTLC on the destination chain has expired, it is not safe anymore to settle (claim) the
|
|
74
|
+
* swap on the destination smart chain.
|
|
75
|
+
*/
|
|
76
|
+
EXPIRED = -1,
|
|
77
|
+
/**
|
|
78
|
+
* Swap quote was created, use {@link FromBTCLNAutoSwap.getAddress} or {@link FromBTCLNAutoSwap.getHyperlink}
|
|
79
|
+
* to get the bolt11 lightning network invoice to pay to initiate the swap, then use the
|
|
80
|
+
* {@link FromBTCLNAutoSwap.waitForPayment} to wait till the lightning network payment is received
|
|
81
|
+
* by the intermediary (LP) and the destination HTLC escrow is created
|
|
82
|
+
*/
|
|
83
|
+
PR_CREATED = 0,
|
|
84
|
+
/**
|
|
85
|
+
* Lightning network payment has been received by the intermediary (LP), but the destination chain
|
|
86
|
+
* HTLC escrow hasn't been created yet. Use {@link FromBTCLNAutoSwap.waitForPayment} to continue waiting
|
|
87
|
+
* till the destination HTLC escrow is created.
|
|
88
|
+
*/
|
|
89
|
+
PR_PAID = 1,
|
|
90
|
+
/**
|
|
91
|
+
* Swap escrow HTLC has been created on the destination chain, wait for automatic settlement by the watchtowers
|
|
92
|
+
* using the {@link FromBTCLNAutoSwap.waitTillClaimed} function or settle manually using the
|
|
93
|
+
* {@link FromBTCLNAutoSwap.claim} or {@link FromBTCLNAutoSwap.txsClaim} function.
|
|
94
|
+
*/
|
|
95
|
+
CLAIM_COMMITED = 2,
|
|
96
|
+
/**
|
|
97
|
+
* Swap successfully settled and funds received on the destination chain
|
|
98
|
+
*/
|
|
99
|
+
CLAIM_CLAIMED = 3
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const FromBTCLNAutoSwapStateDescription = {
|
|
103
|
+
[FromBTCLNAutoSwapState.FAILED]: "Swap has failed as the user didn't settle the HTLC on the destination before expiration",
|
|
104
|
+
[FromBTCLNAutoSwapState.QUOTE_EXPIRED]: "Swap has expired for good and there is no way how it can be executed anymore",
|
|
105
|
+
[FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED]: "A swap is expired, though there is still a chance that it will be processed",
|
|
106
|
+
[FromBTCLNAutoSwapState.EXPIRED]: "Swap HTLC on the destination chain has expired, it is not safe anymore to settle (claim) the swap on the destination smart chain.",
|
|
107
|
+
[FromBTCLNAutoSwapState.PR_CREATED]: "Swap quote was created, pay the bolt11 lightning network invoice to initiate the swap, then wait till the lightning network payment is received by the intermediary (LP) and the destination HTLC escrow is created",
|
|
108
|
+
[FromBTCLNAutoSwapState.PR_PAID]: "Lightning network payment has been received by the intermediary (LP), but the destination chain HTLC escrow hasn't been created yet. Continue waiting till the destination HTLC escrow is created.",
|
|
109
|
+
[FromBTCLNAutoSwapState.CLAIM_COMMITED]: "Swap escrow HTLC has been created on the destination chain, wait for automatic settlement by the watchtowers or settle manually.",
|
|
110
|
+
[FromBTCLNAutoSwapState.CLAIM_CLAIMED]: "Swap successfully settled and funds received on the destination chain"
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export type FromBTCLNAutoSwapInit<T extends SwapData> = IEscrowSwapInit<T> & {
|
|
114
|
+
pr?: string,
|
|
115
|
+
secret?: string,
|
|
116
|
+
initialSwapData: T,
|
|
117
|
+
|
|
118
|
+
btcAmountSwap?: bigint,
|
|
119
|
+
btcAmountGas?: bigint,
|
|
120
|
+
|
|
121
|
+
gasSwapFeeBtc: bigint,
|
|
122
|
+
gasSwapFee: bigint,
|
|
123
|
+
gasPricingInfo?: PriceInfoType,
|
|
124
|
+
|
|
125
|
+
lnurl?: string,
|
|
126
|
+
lnurlK1?: string,
|
|
127
|
+
lnurlCallback?: string
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export function isFromBTCLNAutoSwapInit<T extends SwapData>(obj: any): obj is FromBTCLNAutoSwapInit<T> {
|
|
131
|
+
return (obj.pr==null || typeof obj.pr==="string") &&
|
|
132
|
+
(obj.secret==null || typeof obj.secret==="string") &&
|
|
133
|
+
(obj.btcAmountSwap==null || typeof obj.btcAmountSwap==="bigint") &&
|
|
134
|
+
(obj.btcAmountGas==null || typeof obj.btcAmountGas==="bigint") &&
|
|
135
|
+
typeof obj.gasSwapFeeBtc==="bigint" &&
|
|
136
|
+
typeof obj.gasSwapFee==="bigint" &&
|
|
137
|
+
(obj.gasPricingInfo==null || isPriceInfoType(obj.gasPricingInfo)) &&
|
|
138
|
+
(obj.lnurl==null || typeof(obj.lnurl)==="string") &&
|
|
139
|
+
(obj.lnurlK1==null || typeof(obj.lnurlK1)==="string") &&
|
|
140
|
+
(obj.lnurlCallback==null || typeof(obj.lnurlCallback)==="string") &&
|
|
141
|
+
isIEscrowSwapInit(obj);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* New escrow based (HTLC) swaps for Bitcoin Lightning -> Smart chain swaps not requiring manual settlement on
|
|
146
|
+
* the destination by the user, and instead letting the LP initiate the escrow. Permissionless watchtower network
|
|
147
|
+
* handles the claiming of HTLC, with the swap secret broadcasted over Nostr. Also adds a possibility for the user
|
|
148
|
+
* to receive a native token on the destination chain as part of the swap (a "gas drop" feature).
|
|
149
|
+
*
|
|
150
|
+
* @category Swaps/Lightning → Smart chain
|
|
151
|
+
*/
|
|
152
|
+
export class FromBTCLNAutoSwap<T extends ChainType = ChainType>
|
|
153
|
+
extends IEscrowSwap<T, FromBTCLNAutoDefinition<T>>
|
|
154
|
+
implements IAddressSwap, ISwapWithGasDrop<T>, IClaimableSwap<T, FromBTCLNAutoDefinition<T>, FromBTCLNAutoSwapState> {
|
|
155
|
+
|
|
156
|
+
protected readonly TYPE: SwapType.FROM_BTCLN_AUTO = SwapType.FROM_BTCLN_AUTO;
|
|
157
|
+
/**
|
|
158
|
+
* @internal
|
|
159
|
+
*/
|
|
160
|
+
protected readonly swapStateName = (state: number) => FromBTCLNAutoSwapState[state];
|
|
161
|
+
/**
|
|
162
|
+
* @internal
|
|
163
|
+
*/
|
|
164
|
+
protected readonly swapStateDescription = FromBTCLNAutoSwapStateDescription;
|
|
165
|
+
/**
|
|
166
|
+
* @internal
|
|
167
|
+
*/
|
|
168
|
+
protected readonly logger: LoggerType;
|
|
169
|
+
/**
|
|
170
|
+
* @internal
|
|
171
|
+
*/
|
|
172
|
+
protected readonly inputToken: BtcToken<true> = BitcoinTokens.BTCLN;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Timestamp at which the HTLC was commited on the smart chain side
|
|
176
|
+
* @internal
|
|
177
|
+
*/
|
|
178
|
+
_commitedAt?: number;
|
|
179
|
+
|
|
180
|
+
private readonly lnurlFailSignal: AbortController = new AbortController();
|
|
181
|
+
private readonly usesClaimHashAsId: boolean;
|
|
182
|
+
private readonly initialSwapData: T["Data"];
|
|
183
|
+
|
|
184
|
+
private readonly btcAmountSwap?: bigint;
|
|
185
|
+
private readonly btcAmountGas?: bigint;
|
|
186
|
+
|
|
187
|
+
private readonly gasSwapFeeBtc: bigint;
|
|
188
|
+
private readonly gasSwapFee: bigint;
|
|
189
|
+
|
|
190
|
+
private readonly gasPricingInfo?: PriceInfoType;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* In case the swap is recovered from on-chain data, the pr saved here is just a payment hash,
|
|
194
|
+
* as it is impossible to retrieve the actual lightning network invoice paid purely from on-chain
|
|
195
|
+
* data
|
|
196
|
+
* @private
|
|
197
|
+
*/
|
|
198
|
+
private pr?: string;
|
|
199
|
+
private secret?: string;
|
|
200
|
+
|
|
201
|
+
private lnurl?: string;
|
|
202
|
+
private lnurlK1?: string;
|
|
203
|
+
private lnurlCallback?: string;
|
|
204
|
+
private prPosted?: boolean = false;
|
|
205
|
+
|
|
206
|
+
private broadcastTickCounter: number = 0
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Sets the LNURL data for the swap
|
|
210
|
+
*
|
|
211
|
+
* @internal
|
|
212
|
+
*/
|
|
213
|
+
_setLNURLData(lnurl: string, lnurlK1: string, lnurlCallback: string) {
|
|
214
|
+
this.lnurl = lnurl;
|
|
215
|
+
this.lnurlK1 = lnurlK1;
|
|
216
|
+
this.lnurlCallback = lnurlCallback;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
constructor(wrapper: FromBTCLNAutoWrapper<T>, init: FromBTCLNAutoSwapInit<T["Data"]>);
|
|
220
|
+
constructor(wrapper: FromBTCLNAutoWrapper<T>, obj: any);
|
|
221
|
+
constructor(
|
|
222
|
+
wrapper: FromBTCLNAutoWrapper<T>,
|
|
223
|
+
initOrObject: FromBTCLNAutoSwapInit<T["Data"]> | any
|
|
224
|
+
) {
|
|
225
|
+
if(isFromBTCLNAutoSwapInit(initOrObject) && initOrObject.url!=null) initOrObject.url += "/frombtcln_auto";
|
|
226
|
+
super(wrapper, initOrObject);
|
|
227
|
+
if(isFromBTCLNAutoSwapInit(initOrObject)) {
|
|
228
|
+
this._state = FromBTCLNAutoSwapState.PR_CREATED;
|
|
229
|
+
this.pr = initOrObject.pr;
|
|
230
|
+
this.secret = initOrObject.secret;
|
|
231
|
+
this.initialSwapData = initOrObject.initialSwapData;
|
|
232
|
+
this.btcAmountSwap = initOrObject.btcAmountSwap;
|
|
233
|
+
this.btcAmountGas = initOrObject.btcAmountGas;
|
|
234
|
+
this.gasSwapFeeBtc = initOrObject.gasSwapFeeBtc;
|
|
235
|
+
this.gasSwapFee = initOrObject.gasSwapFee;
|
|
236
|
+
this.gasPricingInfo = initOrObject.gasPricingInfo;
|
|
237
|
+
this.lnurl = initOrObject.lnurl;
|
|
238
|
+
this.lnurlK1 = initOrObject.lnurlK1;
|
|
239
|
+
this.lnurlCallback = initOrObject.lnurlCallback;
|
|
240
|
+
this.usesClaimHashAsId = true;
|
|
241
|
+
} else {
|
|
242
|
+
this.pr = initOrObject.pr;
|
|
243
|
+
this.secret = initOrObject.secret;
|
|
244
|
+
|
|
245
|
+
if(initOrObject.initialSwapData==null) {
|
|
246
|
+
this.initialSwapData = this._data!;
|
|
247
|
+
} else {
|
|
248
|
+
this.initialSwapData = SwapData.deserialize<T["Data"]>(initOrObject.initialSwapData);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
this.btcAmountSwap = toBigInt(initOrObject.btcAmountSwap);
|
|
252
|
+
this.btcAmountGas = toBigInt(initOrObject.btcAmountGas);
|
|
253
|
+
this.gasSwapFeeBtc = toBigInt(initOrObject.gasSwapFeeBtc);
|
|
254
|
+
this.gasSwapFee = toBigInt(initOrObject.gasSwapFee);
|
|
255
|
+
this.gasPricingInfo = deserializePriceInfoType(initOrObject.gasPricingInfo);
|
|
256
|
+
|
|
257
|
+
this._commitTxId = initOrObject.commitTxId;
|
|
258
|
+
this._claimTxId = initOrObject.claimTxId;
|
|
259
|
+
this._commitedAt = initOrObject.commitedAt;
|
|
260
|
+
|
|
261
|
+
this.lnurl = initOrObject.lnurl;
|
|
262
|
+
this.lnurlK1 = initOrObject.lnurlK1;
|
|
263
|
+
this.lnurlCallback = initOrObject.lnurlCallback;
|
|
264
|
+
this.prPosted = initOrObject.prPosted;
|
|
265
|
+
this.usesClaimHashAsId = initOrObject.usesClaimHashAsId ?? false;
|
|
266
|
+
}
|
|
267
|
+
this.tryRecomputeSwapPrice();
|
|
268
|
+
this.logger = getLogger("FromBTCLNAuto("+this.getIdentifierHashString()+"): ");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* @inheritDoc
|
|
273
|
+
* @internal
|
|
274
|
+
*/
|
|
275
|
+
protected getSwapData(): T["Data"] {
|
|
276
|
+
return this._data ?? this.initialSwapData;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* @inheritDoc
|
|
281
|
+
* @internal
|
|
282
|
+
*/
|
|
283
|
+
protected upgradeVersion() { /*NOOP*/ }
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @inheritDoc
|
|
287
|
+
* @internal
|
|
288
|
+
*/
|
|
289
|
+
protected tryRecomputeSwapPrice() {
|
|
290
|
+
if(this.pricingInfo==null || this.btcAmountSwap==null) return;
|
|
291
|
+
if(this.pricingInfo.swapPriceUSatPerToken==null) {
|
|
292
|
+
const priceUsdPerBtc = this.pricingInfo.realPriceUsdPerBitcoin;
|
|
293
|
+
this.pricingInfo = this.wrapper._prices.recomputePriceInfoReceive(
|
|
294
|
+
this.chainIdentifier,
|
|
295
|
+
this.btcAmountSwap,
|
|
296
|
+
this.pricingInfo.satsBaseFee,
|
|
297
|
+
this.pricingInfo.feePPM,
|
|
298
|
+
this.getOutputAmountWithoutFee(),
|
|
299
|
+
this.getSwapData().getToken()
|
|
300
|
+
);
|
|
301
|
+
this.pricingInfo.realPriceUsdPerBitcoin = priceUsdPerBtc;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
//////////////////////////////
|
|
307
|
+
//// Pricing
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* @inheritDoc
|
|
311
|
+
*/
|
|
312
|
+
async refreshPriceData(): Promise<void> {
|
|
313
|
+
if(this.pricingInfo==null || this.btcAmountSwap==null) return;
|
|
314
|
+
const usdPricePerBtc = this.pricingInfo.realPriceUsdPerBitcoin;
|
|
315
|
+
this.pricingInfo = await this.wrapper._prices.isValidAmountReceive(
|
|
316
|
+
this.chainIdentifier,
|
|
317
|
+
this.btcAmountSwap,
|
|
318
|
+
this.pricingInfo.satsBaseFee,
|
|
319
|
+
this.pricingInfo.feePPM,
|
|
320
|
+
this.getOutputAmountWithoutFee(),
|
|
321
|
+
this.getSwapData().getToken(),
|
|
322
|
+
undefined,
|
|
323
|
+
undefined,
|
|
324
|
+
this.swapFeeBtc
|
|
325
|
+
);
|
|
326
|
+
this.pricingInfo.realPriceUsdPerBitcoin = usdPricePerBtc;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
//////////////////////////////
|
|
331
|
+
//// Getters & utils
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* @inheritDoc
|
|
335
|
+
* @internal
|
|
336
|
+
*/
|
|
337
|
+
_getEscrowHash(): string | null {
|
|
338
|
+
//Use claim hash in case the data is not yet known
|
|
339
|
+
return this._data == null ? this.initialSwapData?.getClaimHash() : this._data?.getEscrowHash();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* @inheritDoc
|
|
344
|
+
* @internal
|
|
345
|
+
*/
|
|
346
|
+
_getInitiator(): string {
|
|
347
|
+
return this.getSwapData().getClaimer();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* @inheritDoc
|
|
352
|
+
*/
|
|
353
|
+
getId(): string {
|
|
354
|
+
return this.getIdentifierHashString();
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* @inheritDoc
|
|
359
|
+
*/
|
|
360
|
+
getOutputAddress(): string | null {
|
|
361
|
+
return this._getInitiator();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* @inheritDoc
|
|
366
|
+
*/
|
|
367
|
+
getOutputTxId(): string | null {
|
|
368
|
+
return this._claimTxId ?? null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* @inheritDoc
|
|
373
|
+
*/
|
|
374
|
+
requiresAction(): boolean {
|
|
375
|
+
return this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* @inheritDoc
|
|
380
|
+
* @internal
|
|
381
|
+
*/
|
|
382
|
+
protected getIdentifierHashString(): string {
|
|
383
|
+
const id: string = this.usesClaimHashAsId
|
|
384
|
+
? this.getClaimHash()
|
|
385
|
+
: this.getPaymentHash()!.toString("hex");
|
|
386
|
+
if(this._randomNonce==null) return id;
|
|
387
|
+
return id + this._randomNonce;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Returns the payment hash of the swap and lightning network invoice, or `null` if not known (i.e. if
|
|
392
|
+
* the swap was recovered from on-chain data, the payment hash might not be known)
|
|
393
|
+
*
|
|
394
|
+
* @internal
|
|
395
|
+
*/
|
|
396
|
+
protected getPaymentHash(): Buffer | null {
|
|
397
|
+
if(this.pr==null) return null;
|
|
398
|
+
if(this.pr.toLowerCase().startsWith("ln")) {
|
|
399
|
+
const parsed = bolt11Decode(this.pr);
|
|
400
|
+
if(parsed.tagsObject.payment_hash==null) throw new Error("Swap invoice has no payment hash field!");
|
|
401
|
+
return Buffer.from(parsed.tagsObject.payment_hash, "hex");
|
|
402
|
+
}
|
|
403
|
+
return Buffer.from(this.pr, "hex");
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* @inheritDoc
|
|
408
|
+
*/
|
|
409
|
+
getInputAddress(): string | null {
|
|
410
|
+
return this.lnurl ?? this.pr ?? null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* @inheritDoc
|
|
415
|
+
*/
|
|
416
|
+
getInputTxId(): string | null {
|
|
417
|
+
const paymentHash = this.getPaymentHash();
|
|
418
|
+
if(paymentHash==null) return null;
|
|
419
|
+
return paymentHash.toString("hex");
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Returns the lightning network BOLT11 invoice that needs to be paid as an input to the swap
|
|
424
|
+
*/
|
|
425
|
+
getAddress(): string {
|
|
426
|
+
return this.pr ?? "";
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* @inheritDoc
|
|
431
|
+
*/
|
|
432
|
+
getHyperlink(): string {
|
|
433
|
+
return this.pr==null ? "" : "lightning:"+this.pr.toUpperCase();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Returns the timeout time (in UNIX milliseconds) when the swap will definitelly be considered as expired
|
|
438
|
+
* if the LP doesn't make it expired sooner
|
|
439
|
+
*/
|
|
440
|
+
getDefinitiveExpiryTime(): number {
|
|
441
|
+
if(this.pr==null || !this.pr.toLowerCase().startsWith("ln")) return 0;
|
|
442
|
+
const decoded = bolt11Decode(this.pr);
|
|
443
|
+
if(decoded.tagsObject.min_final_cltv_expiry==null) throw new Error("Swap invoice doesn't contain final ctlv delta field!");
|
|
444
|
+
if(decoded.timeExpireDate==null) throw new Error("Swap invoice doesn't contain expiry date field!");
|
|
445
|
+
const finalCltvExpiryDelta = decoded.tagsObject.min_final_cltv_expiry ?? 144;
|
|
446
|
+
const finalCltvExpiryDelay = finalCltvExpiryDelta * this.wrapper._options.bitcoinBlocktime * this.wrapper._options.safetyFactor;
|
|
447
|
+
return (decoded.timeExpireDate + finalCltvExpiryDelay)*1000;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Returns timeout time (in UNIX milliseconds) when the swap htlc will expire
|
|
452
|
+
*/
|
|
453
|
+
getHtlcTimeoutTime(): number | null {
|
|
454
|
+
return this._data==null ? null : Number(this.wrapper._getHtlcTimeout(this._data))*1000;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* @inheritDoc
|
|
459
|
+
*/
|
|
460
|
+
isFinished(): boolean {
|
|
461
|
+
return this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED || this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED || this._state===FromBTCLNAutoSwapState.FAILED;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* @inheritDoc
|
|
466
|
+
*/
|
|
467
|
+
isClaimable(): boolean {
|
|
468
|
+
return this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* @inheritDoc
|
|
473
|
+
*/
|
|
474
|
+
isSuccessful(): boolean {
|
|
475
|
+
return this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* @inheritDoc
|
|
480
|
+
*/
|
|
481
|
+
isFailed(): boolean {
|
|
482
|
+
return this._state===FromBTCLNAutoSwapState.FAILED || this._state===FromBTCLNAutoSwapState.EXPIRED;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* @inheritDoc
|
|
487
|
+
*/
|
|
488
|
+
isInProgress(): boolean {
|
|
489
|
+
return (this._state===FromBTCLNAutoSwapState.PR_CREATED && this.initiated) ||
|
|
490
|
+
(this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED && this.initiated) ||
|
|
491
|
+
this._state===FromBTCLNAutoSwapState.PR_PAID ||
|
|
492
|
+
this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* @inheritDoc
|
|
497
|
+
*/
|
|
498
|
+
isQuoteExpired(): boolean {
|
|
499
|
+
return this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* @inheritDoc
|
|
504
|
+
*/
|
|
505
|
+
isQuoteSoftExpired(): boolean {
|
|
506
|
+
return this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* @inheritDoc
|
|
511
|
+
*/
|
|
512
|
+
_verifyQuoteDefinitelyExpired(): Promise<boolean> {
|
|
513
|
+
return Promise.resolve(this.getDefinitiveExpiryTime()<Date.now());
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* @inheritDoc
|
|
518
|
+
*/
|
|
519
|
+
_verifyQuoteValid(): Promise<boolean> {
|
|
520
|
+
return Promise.resolve(this.getQuoteExpiry()>Date.now());
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
//////////////////////////////
|
|
525
|
+
//// Amounts & fees
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Returns the satoshi amount of the lightning network invoice, or `null` if the lightning network
|
|
529
|
+
* invoice is not known (i.e. when the swap was recovered from on-chain data, the paid invoice
|
|
530
|
+
* cannot be recovered because it is purely off-chain)
|
|
531
|
+
*
|
|
532
|
+
* @internal
|
|
533
|
+
*/
|
|
534
|
+
protected getLightningInvoiceSats(): bigint | null {
|
|
535
|
+
if(this.pr==null || !this.pr.toLowerCase().startsWith("ln")) return null;
|
|
536
|
+
|
|
537
|
+
const parsed = bolt11Decode(this.pr);
|
|
538
|
+
if(parsed.millisatoshis==null) throw new Error("Swap invoice doesn't contain msat amount field!");
|
|
539
|
+
return (BigInt(parsed.millisatoshis) + 999n) / 1000n;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Returns the watchtower fee paid in BTC satoshis, or null if known (i.e. if the swap was recovered from
|
|
544
|
+
* on-chain data)
|
|
545
|
+
*
|
|
546
|
+
* @protected
|
|
547
|
+
*/
|
|
548
|
+
protected getWatchtowerFeeAmountBtc(): bigint | null {
|
|
549
|
+
if(this.btcAmountGas==null) return null;
|
|
550
|
+
return (this.btcAmountGas - this.gasSwapFeeBtc) * this.getSwapData().getClaimerBounty() / this.getSwapData().getTotalDeposit();
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Returns the input amount for the actual swap (excluding the input amount used to cover the "gas drop"
|
|
555
|
+
* part of the swap), excluding fees
|
|
556
|
+
*
|
|
557
|
+
* @internal
|
|
558
|
+
*/
|
|
559
|
+
protected getInputSwapAmountWithoutFee(): bigint | null {
|
|
560
|
+
if(this.btcAmountSwap==null) return null;
|
|
561
|
+
return this.btcAmountSwap - this.swapFeeBtc;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Returns the input amount purely for the "gas drop" part of the swap (this much BTC in sats will be
|
|
566
|
+
* swapped into the native gas token on the destination chain), excluding fees
|
|
567
|
+
*
|
|
568
|
+
* @internal
|
|
569
|
+
*/
|
|
570
|
+
protected getInputGasAmountWithoutFee(): bigint | null {
|
|
571
|
+
if(this.btcAmountGas==null) return null;
|
|
572
|
+
return this.btcAmountGas - this.gasSwapFeeBtc;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Get total btc amount in sats on the input, excluding the swap fee and watchtower fee
|
|
577
|
+
*
|
|
578
|
+
* @internal
|
|
579
|
+
*/
|
|
580
|
+
protected getInputAmountWithoutFee(): bigint | null {
|
|
581
|
+
if(this.btcAmountGas==null || this.btcAmountSwap==null) return null;
|
|
582
|
+
return this.getInputSwapAmountWithoutFee()! + this.getInputGasAmountWithoutFee()! - this.getWatchtowerFeeAmountBtc()!;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Returns the "would be" output amount if the swap charged no swap fee
|
|
587
|
+
*
|
|
588
|
+
* @internal
|
|
589
|
+
*/
|
|
590
|
+
protected getOutputAmountWithoutFee(): bigint {
|
|
591
|
+
return this.getSwapData().getAmount() + this.swapFee;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* @inheritDoc
|
|
596
|
+
*/
|
|
597
|
+
getInputToken(): BtcToken<true> {
|
|
598
|
+
return BitcoinTokens.BTCLN;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* @inheritDoc
|
|
603
|
+
*/
|
|
604
|
+
getInput(): TokenAmount<BtcToken<true>> {
|
|
605
|
+
return toTokenAmount(this.getLightningInvoiceSats(), this.inputToken, this.wrapper._prices, this.pricingInfo);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* @inheritDoc
|
|
610
|
+
*/
|
|
611
|
+
getInputWithoutFee(): TokenAmount<BtcToken<true>> {
|
|
612
|
+
return toTokenAmount(this.getInputAmountWithoutFee(), this.inputToken, this.wrapper._prices, this.pricingInfo);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* @inheritDoc
|
|
617
|
+
*/
|
|
618
|
+
getOutputToken(): SCToken<T["ChainId"]> {
|
|
619
|
+
return this.wrapper._tokens[this.getSwapData().getToken()];
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* @inheritDoc
|
|
624
|
+
*/
|
|
625
|
+
getOutput(): TokenAmount<SCToken<T["ChainId"]>, true> {
|
|
626
|
+
return toTokenAmount(this.getSwapData().getAmount(), this.wrapper._tokens[this.getSwapData().getToken()], this.wrapper._prices, this.pricingInfo);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* @inheritDoc
|
|
631
|
+
*/
|
|
632
|
+
getGasDropOutput(): TokenAmount<SCToken<T["ChainId"]>, true> {
|
|
633
|
+
return toTokenAmount(
|
|
634
|
+
this.getSwapData().getSecurityDeposit() - this.getSwapData().getClaimerBounty(),
|
|
635
|
+
this.wrapper._tokens[this.getSwapData().getDepositToken()], this.wrapper._prices, this.gasPricingInfo
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Returns the swap fee charged by the intermediary (LP) on this swap
|
|
641
|
+
*
|
|
642
|
+
* @internal
|
|
643
|
+
*/
|
|
644
|
+
protected getSwapFee(): Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>> {
|
|
645
|
+
if(this.pricingInfo==null) throw new Error("No pricing info known, cannot estimate fee!");
|
|
646
|
+
|
|
647
|
+
const outputToken = this.wrapper._tokens[this.getSwapData().getToken()];
|
|
648
|
+
const gasSwapFeeInOutputToken = this.gasSwapFeeBtc
|
|
649
|
+
* (10n ** BigInt(outputToken.decimals))
|
|
650
|
+
* 1_000_000n
|
|
651
|
+
/ this.pricingInfo.swapPriceUSatPerToken;
|
|
652
|
+
|
|
653
|
+
const feeWithoutBaseFee = this.gasSwapFeeBtc + this.swapFeeBtc - this.pricingInfo.satsBaseFee;
|
|
654
|
+
const inputSats = this.getLightningInvoiceSats();
|
|
655
|
+
const swapFeePPM = inputSats!=null
|
|
656
|
+
? feeWithoutBaseFee * 1000000n / (inputSats - this.swapFeeBtc - this.gasSwapFeeBtc)
|
|
657
|
+
: 0n;
|
|
658
|
+
|
|
659
|
+
const amountInSrcToken = toTokenAmount(this.swapFeeBtc + this.gasSwapFeeBtc, BitcoinTokens.BTCLN, this.wrapper._prices, this.pricingInfo);
|
|
660
|
+
return {
|
|
661
|
+
amountInSrcToken,
|
|
662
|
+
amountInDstToken: toTokenAmount(this.swapFee + gasSwapFeeInOutputToken, outputToken, this.wrapper._prices, this.pricingInfo),
|
|
663
|
+
currentUsdValue: amountInSrcToken.currentUsdValue,
|
|
664
|
+
pastUsdValue: amountInSrcToken.pastUsdValue,
|
|
665
|
+
usdValue: amountInSrcToken.usdValue,
|
|
666
|
+
composition: {
|
|
667
|
+
base: toTokenAmount(this.pricingInfo.satsBaseFee, BitcoinTokens.BTCLN, this.wrapper._prices, this.pricingInfo),
|
|
668
|
+
percentage: ppmToPercentage(swapFeePPM)
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Returns the fee to be paid to watchtowers on the destination chain to automatically
|
|
675
|
+
* process and settle this swap without requiring any user interaction
|
|
676
|
+
*
|
|
677
|
+
* @internal
|
|
678
|
+
*/
|
|
679
|
+
protected getWatchtowerFee(): Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>> {
|
|
680
|
+
if(this.pricingInfo==null) throw new Error("No pricing info known, cannot estimate fee!");
|
|
681
|
+
|
|
682
|
+
const btcWatchtowerFee = this.getWatchtowerFeeAmountBtc();
|
|
683
|
+
const outputToken = this.wrapper._tokens[this.getSwapData().getToken()];
|
|
684
|
+
const watchtowerFeeInOutputToken = btcWatchtowerFee==null ? 0n : btcWatchtowerFee
|
|
685
|
+
* (10n ** BigInt(outputToken.decimals))
|
|
686
|
+
* 1_000_000n
|
|
687
|
+
/ this.pricingInfo.swapPriceUSatPerToken;
|
|
688
|
+
|
|
689
|
+
const amountInSrcToken = toTokenAmount(btcWatchtowerFee, BitcoinTokens.BTCLN, this.wrapper._prices, this.pricingInfo);
|
|
690
|
+
return {
|
|
691
|
+
amountInSrcToken,
|
|
692
|
+
amountInDstToken: toTokenAmount(watchtowerFeeInOutputToken, outputToken, this.wrapper._prices, this.pricingInfo),
|
|
693
|
+
currentUsdValue: amountInSrcToken.currentUsdValue,
|
|
694
|
+
usdValue: amountInSrcToken.usdValue,
|
|
695
|
+
pastUsdValue: amountInSrcToken.pastUsdValue
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* @inheritDoc
|
|
701
|
+
*/
|
|
702
|
+
getFee(): Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>> {
|
|
703
|
+
const swapFee = this.getSwapFee();
|
|
704
|
+
const watchtowerFee = this.getWatchtowerFee();
|
|
705
|
+
|
|
706
|
+
const amountInSrcToken = toTokenAmount(
|
|
707
|
+
swapFee.amountInSrcToken.rawAmount + watchtowerFee.amountInSrcToken.rawAmount,
|
|
708
|
+
BitcoinTokens.BTCLN, this.wrapper._prices, this.pricingInfo
|
|
709
|
+
);
|
|
710
|
+
return {
|
|
711
|
+
amountInSrcToken,
|
|
712
|
+
amountInDstToken: toTokenAmount(
|
|
713
|
+
swapFee.amountInDstToken.rawAmount + watchtowerFee.amountInDstToken.rawAmount,
|
|
714
|
+
this.wrapper._tokens[this.getSwapData().getToken()], this.wrapper._prices, this.pricingInfo
|
|
715
|
+
),
|
|
716
|
+
currentUsdValue: amountInSrcToken.currentUsdValue,
|
|
717
|
+
usdValue: amountInSrcToken.usdValue,
|
|
718
|
+
pastUsdValue: amountInSrcToken.pastUsdValue
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* @inheritDoc
|
|
724
|
+
*/
|
|
725
|
+
getFeeBreakdown(): [
|
|
726
|
+
{type: FeeType.SWAP, fee: Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>>},
|
|
727
|
+
{type: FeeType.NETWORK_OUTPUT, fee: Fee<T["ChainId"], BtcToken<true>, SCToken<T["ChainId"]>>}
|
|
728
|
+
] {
|
|
729
|
+
return [
|
|
730
|
+
{
|
|
731
|
+
type: FeeType.SWAP,
|
|
732
|
+
fee: this.getSwapFee()
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
type: FeeType.NETWORK_OUTPUT,
|
|
736
|
+
fee: this.getWatchtowerFee()
|
|
737
|
+
}
|
|
738
|
+
];
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
private isValidSecretPreimage(secret: string) {
|
|
742
|
+
const paymentHash = Buffer.from(sha256(Buffer.from(secret, "hex")));
|
|
743
|
+
const claimHash = this._contract.getHashForHtlc(paymentHash).toString("hex");
|
|
744
|
+
return this.getSwapData().getClaimHash()===claimHash;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Sets the secret preimage for the swap, in case it is not known already
|
|
749
|
+
*
|
|
750
|
+
* @param secret Secret preimage that matches the expected payment hash
|
|
751
|
+
*
|
|
752
|
+
* @throws {Error} If an invalid secret preimage is provided
|
|
753
|
+
*/
|
|
754
|
+
async setSecretPreimage(secret: string) {
|
|
755
|
+
if(!this.isValidSecretPreimage(secret)) throw new Error("Invalid secret preimage provided, hash doesn't match!");
|
|
756
|
+
this.secret = secret;
|
|
757
|
+
await this._broadcastSecret().catch(e => this.logger.error("setSecretPreimage(): Failed to broadcast swap secret: ", e));
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Returns whether the secret preimage for this swap is known
|
|
762
|
+
*/
|
|
763
|
+
hasSecretPreimage(): boolean {
|
|
764
|
+
return this.secret != null;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
//////////////////////////////
|
|
769
|
+
//// Execution
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Executes the swap with the provided bitcoin lightning network wallet or LNURL
|
|
773
|
+
*
|
|
774
|
+
* @param walletOrLnurlWithdraw Bitcoin lightning wallet to use to pay the lightning network invoice, or an LNURL-withdraw
|
|
775
|
+
* link, wallet is not required and the LN invoice can be paid externally as well (just pass null or undefined here)
|
|
776
|
+
* @param callbacks Callbacks to track the progress of the swap
|
|
777
|
+
* @param options Optional options for the swap like AbortSignal, and timeouts/intervals
|
|
778
|
+
* @param options.secret A swap secret to broadcast to watchtowers, generally only needed if the swap
|
|
779
|
+
* was recovered from on-chain data, or the pre-image was generated outside the SDK
|
|
780
|
+
*
|
|
781
|
+
* @returns {boolean} Whether a swap was settled automatically by swap watchtowers or requires manual claim by the
|
|
782
|
+
* user, in case `false` is returned the user should call `swap.claim()` to settle the swap on the destination manually
|
|
783
|
+
*/
|
|
784
|
+
async execute(
|
|
785
|
+
walletOrLnurlWithdraw?: MinimalLightningNetworkWalletInterface | LNURLWithdraw | string | null | undefined,
|
|
786
|
+
callbacks?: {
|
|
787
|
+
onSourceTransactionReceived?: (sourceTxId: string) => void,
|
|
788
|
+
onSwapSettled?: (destinationTxId: string) => void
|
|
789
|
+
},
|
|
790
|
+
options?: {
|
|
791
|
+
abortSignal?: AbortSignal,
|
|
792
|
+
lightningTxCheckIntervalSeconds?: number,
|
|
793
|
+
maxWaitTillAutomaticSettlementSeconds?: number,
|
|
794
|
+
secret?: string
|
|
795
|
+
}
|
|
796
|
+
): Promise<boolean> {
|
|
797
|
+
if(this._state===FromBTCLNAutoSwapState.FAILED) throw new Error("Swap failed!");
|
|
798
|
+
if(this._state===FromBTCLNAutoSwapState.EXPIRED) throw new Error("Swap HTLC expired!");
|
|
799
|
+
if(this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) throw new Error("Swap quote expired!");
|
|
800
|
+
if(this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED) throw new Error("Swap already settled!");
|
|
801
|
+
|
|
802
|
+
let abortSignal = options?.abortSignal;
|
|
803
|
+
|
|
804
|
+
if(this._state===FromBTCLNAutoSwapState.PR_CREATED) {
|
|
805
|
+
if(walletOrLnurlWithdraw!=null && this.lnurl==null) {
|
|
806
|
+
if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
|
|
807
|
+
throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
|
|
808
|
+
|
|
809
|
+
if(typeof(walletOrLnurlWithdraw)==="string" || isLNURLWithdraw(walletOrLnurlWithdraw)) {
|
|
810
|
+
await this.settleWithLNURLWithdraw(walletOrLnurlWithdraw);
|
|
811
|
+
} else {
|
|
812
|
+
const paymentPromise = walletOrLnurlWithdraw.payInvoice(this.pr);
|
|
813
|
+
|
|
814
|
+
const abortController = new AbortController();
|
|
815
|
+
paymentPromise.catch(e => abortController.abort(e));
|
|
816
|
+
if(options?.abortSignal!=null) options.abortSignal.addEventListener("abort", () => abortController.abort(options?.abortSignal?.reason));
|
|
817
|
+
abortSignal = abortController.signal;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.PR_PAID) {
|
|
823
|
+
const paymentSuccess = await this.waitForPayment(callbacks?.onSourceTransactionReceived, options?.lightningTxCheckIntervalSeconds, abortSignal);
|
|
824
|
+
if (!paymentSuccess) throw new Error("Failed to receive lightning network payment");
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if((this._state as FromBTCLNAutoSwapState)===FromBTCLNAutoSwapState.CLAIM_CLAIMED) return true;
|
|
828
|
+
|
|
829
|
+
if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED) {
|
|
830
|
+
if(this.secret==null && options?.secret==null)
|
|
831
|
+
throw new Error("Tried to wait till settlement, but no secret pre-image is known, please pass the secret pre-image as an argument!");
|
|
832
|
+
const success = await this.waitTillClaimed(options?.maxWaitTillAutomaticSettlementSeconds ?? 60, options?.abortSignal, options?.secret);
|
|
833
|
+
if (success && callbacks?.onSwapSettled != null) callbacks.onSwapSettled(this.getOutputTxId()!);
|
|
834
|
+
return success;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
throw new Error("Invalid state reached!");
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* @internal
|
|
842
|
+
*/
|
|
843
|
+
protected async _getExecutionStatus(options?: {
|
|
844
|
+
maxWaitTillAutomaticSettlementSeconds?: number,
|
|
845
|
+
secret?: string
|
|
846
|
+
}) {
|
|
847
|
+
if(options?.secret!=null) await this.setSecretPreimage(options.secret);
|
|
848
|
+
|
|
849
|
+
const state = this._state;
|
|
850
|
+
const now = Date.now();
|
|
851
|
+
|
|
852
|
+
let lightningPaymentStatus: SwapExecutionStepPayment<"LIGHTNING">["status"] = "inactive";
|
|
853
|
+
let destinationSettlementStatus: SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">["status"] = "inactive";
|
|
854
|
+
let buildCurrentAction: (actionOptions?: {
|
|
855
|
+
manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"]
|
|
856
|
+
}) => Promise<
|
|
857
|
+
SwapExecutionActionSendToAddress<true> |
|
|
858
|
+
SwapExecutionActionWait<"LP" | "SETTLEMENT"> |
|
|
859
|
+
SwapExecutionActionSignSmartChainTx<T> |
|
|
860
|
+
undefined
|
|
861
|
+
> = async () => undefined;
|
|
862
|
+
|
|
863
|
+
switch(state) {
|
|
864
|
+
case FromBTCLNAutoSwapState.PR_CREATED: {
|
|
865
|
+
const quoteValid = await this._verifyQuoteValid();
|
|
866
|
+
lightningPaymentStatus = quoteValid ? "awaiting" : "soft_expired";
|
|
867
|
+
if(quoteValid && this.pr!=null && this.pr.toLowerCase().startsWith("ln")) {
|
|
868
|
+
buildCurrentAction = this._buildLightningPaymentAction.bind(this);
|
|
869
|
+
}
|
|
870
|
+
break;
|
|
871
|
+
}
|
|
872
|
+
case FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED:
|
|
873
|
+
lightningPaymentStatus = "soft_expired";
|
|
874
|
+
break;
|
|
875
|
+
case FromBTCLNAutoSwapState.QUOTE_EXPIRED:
|
|
876
|
+
lightningPaymentStatus = "expired";
|
|
877
|
+
break;
|
|
878
|
+
case FromBTCLNAutoSwapState.PR_PAID:
|
|
879
|
+
lightningPaymentStatus = "received";
|
|
880
|
+
destinationSettlementStatus = "waiting_lp";
|
|
881
|
+
buildCurrentAction = this._buildWaitLpAction.bind(this);
|
|
882
|
+
break;
|
|
883
|
+
case FromBTCLNAutoSwapState.CLAIM_COMMITED:
|
|
884
|
+
lightningPaymentStatus = "received";
|
|
885
|
+
if(
|
|
886
|
+
this._commitedAt==null ||
|
|
887
|
+
options?.maxWaitTillAutomaticSettlementSeconds===0 ||
|
|
888
|
+
(now - this._commitedAt) > (options?.maxWaitTillAutomaticSettlementSeconds ?? 60)*1000
|
|
889
|
+
) {
|
|
890
|
+
destinationSettlementStatus = "awaiting_manual";
|
|
891
|
+
if(this.hasSecretPreimage()) {
|
|
892
|
+
//TODO: Maybe add an action that would prompt the user to reveal the pre-image
|
|
893
|
+
buildCurrentAction = this._buildClaimSmartChainTxAction.bind(this);
|
|
894
|
+
}
|
|
895
|
+
} else {
|
|
896
|
+
destinationSettlementStatus = "awaiting_automatic";
|
|
897
|
+
//TODO: Maybe add an action that would prompt the user to reveal the pre-image
|
|
898
|
+
buildCurrentAction = this._buildWaitSettlementAction.bind(this, options?.maxWaitTillAutomaticSettlementSeconds);
|
|
899
|
+
}
|
|
900
|
+
break;
|
|
901
|
+
case FromBTCLNAutoSwapState.CLAIM_CLAIMED:
|
|
902
|
+
lightningPaymentStatus = "confirmed";
|
|
903
|
+
destinationSettlementStatus = "settled";
|
|
904
|
+
break;
|
|
905
|
+
case FromBTCLNAutoSwapState.EXPIRED:
|
|
906
|
+
case FromBTCLNAutoSwapState.FAILED:
|
|
907
|
+
lightningPaymentStatus = "expired";
|
|
908
|
+
destinationSettlementStatus = "expired";
|
|
909
|
+
break;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
return {
|
|
913
|
+
steps: [
|
|
914
|
+
{
|
|
915
|
+
type: "Payment",
|
|
916
|
+
side: "source",
|
|
917
|
+
chain: "LIGHTNING",
|
|
918
|
+
title: "Lightning payment",
|
|
919
|
+
description: "Pay the Lightning network invoice to initiate the swap",
|
|
920
|
+
status: lightningPaymentStatus,
|
|
921
|
+
initTxId: this.getInputTxId(),
|
|
922
|
+
settleTxId: lightningPaymentStatus==="confirmed" ? this.getInputTxId() : undefined
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
type: "Settlement",
|
|
926
|
+
side: "destination",
|
|
927
|
+
chain: this.chainIdentifier,
|
|
928
|
+
title: "Destination settlement",
|
|
929
|
+
description: `Wait for the LP to initiate on the ${this.chainIdentifier} side, then wait for automatic settlement, or settle manually if it takes too long`,
|
|
930
|
+
status: destinationSettlementStatus,
|
|
931
|
+
initTxId: this._commitTxId,
|
|
932
|
+
settleTxId: this._claimTxId
|
|
933
|
+
}
|
|
934
|
+
] as [
|
|
935
|
+
SwapExecutionStepPayment<"LIGHTNING">,
|
|
936
|
+
SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
|
|
937
|
+
],
|
|
938
|
+
buildCurrentAction,
|
|
939
|
+
state
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* @internal
|
|
945
|
+
*/
|
|
946
|
+
private async _buildLightningPaymentAction(): Promise<SwapExecutionActionSendToAddress<true>> {
|
|
947
|
+
return {
|
|
948
|
+
type: "SendToAddress",
|
|
949
|
+
name: "Deposit on Lightning",
|
|
950
|
+
description: "Pay the lightning network invoice to initiate the swap",
|
|
951
|
+
chain: "LIGHTNING",
|
|
952
|
+
txs: [{
|
|
953
|
+
type: "BOLT11_PAYMENT_REQUEST",
|
|
954
|
+
address: this.getAddress(),
|
|
955
|
+
hyperlink: this.getHyperlink(),
|
|
956
|
+
amount: this.getInput()
|
|
957
|
+
}],
|
|
958
|
+
waitForTransactions: async (
|
|
959
|
+
maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal
|
|
960
|
+
) => {
|
|
961
|
+
const abortController = extendAbortController(
|
|
962
|
+
abortSignal, maxWaitTimeSeconds, "Timed out waiting for lightning payment"
|
|
963
|
+
);
|
|
964
|
+
const success = await this._waitForLpPaymentReceived(pollIntervalSeconds, abortController.signal);
|
|
965
|
+
if(!success) throw new Error("Quote expired while waiting for Lightning payment");
|
|
966
|
+
|
|
967
|
+
return this.getInputTxId()!;
|
|
968
|
+
}
|
|
969
|
+
} as SwapExecutionActionSendToAddress<true>;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* @internal
|
|
974
|
+
*/
|
|
975
|
+
private async _buildWaitLpAction(): Promise<SwapExecutionActionWait<"LP">> {
|
|
976
|
+
return {
|
|
977
|
+
type: "Wait",
|
|
978
|
+
name: "Awaiting LP payout",
|
|
979
|
+
description: "Wait for the LP to create the swap HTLC on the destination smart chain",
|
|
980
|
+
pollTimeSeconds: 5,
|
|
981
|
+
expectedTimeSeconds: 10,
|
|
982
|
+
wait: async (
|
|
983
|
+
maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal
|
|
984
|
+
) => {
|
|
985
|
+
const abortController = extendAbortController(
|
|
986
|
+
abortSignal, maxWaitTimeSeconds, "Timed out waiting for LP payout"
|
|
987
|
+
);
|
|
988
|
+
await this.waitTillCommited(pollIntervalSeconds, abortController.signal);
|
|
989
|
+
}
|
|
990
|
+
} as SwapExecutionActionWait<"LP">;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* @internal
|
|
995
|
+
*/
|
|
996
|
+
private async _buildWaitSettlementAction(
|
|
997
|
+
maxWaitTillAutomaticSettlementSeconds?: number
|
|
998
|
+
): Promise<SwapExecutionActionWait<"SETTLEMENT">> {
|
|
999
|
+
return {
|
|
1000
|
+
type: "Wait",
|
|
1001
|
+
name: "Automatic settlement",
|
|
1002
|
+
description: "Wait for automatic settlement by the watchtower",
|
|
1003
|
+
pollTimeSeconds: 5,
|
|
1004
|
+
expectedTimeSeconds: 10,
|
|
1005
|
+
wait: async (
|
|
1006
|
+
maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal
|
|
1007
|
+
) => {
|
|
1008
|
+
await this.waitTillClaimed(
|
|
1009
|
+
maxWaitTimeSeconds ?? maxWaitTillAutomaticSettlementSeconds ?? 60,
|
|
1010
|
+
abortSignal,
|
|
1011
|
+
undefined,
|
|
1012
|
+
pollIntervalSeconds
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
} as SwapExecutionActionWait<"SETTLEMENT">;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/**
|
|
1019
|
+
* @inheritDoc
|
|
1020
|
+
* @internal
|
|
1021
|
+
*/
|
|
1022
|
+
async _submitExecutionTransactions(
|
|
1023
|
+
txs: (T["SignedTXType"] | string)[],
|
|
1024
|
+
abortSignal?: AbortSignal,
|
|
1025
|
+
requiredStates?: FromBTCLNAutoSwapState[],
|
|
1026
|
+
idempotent?: boolean
|
|
1027
|
+
): Promise<string[]> {
|
|
1028
|
+
const parsedTxs: T["SignedTXType"][] = [];
|
|
1029
|
+
for(let tx of txs) {
|
|
1030
|
+
parsedTxs.push(typeof(tx)==="string" ? await this.wrapper._chain.deserializeSignedTx(tx) : tx);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
if(idempotent) {
|
|
1034
|
+
// Handle idempotent calls
|
|
1035
|
+
if(this.wrapper._chain.getTxId!=null) {
|
|
1036
|
+
const txIds = await Promise.all(parsedTxs.map(tx => this.wrapper._chain.getTxId!(tx)));
|
|
1037
|
+
const foundTxId = txIds.find(txId => this._claimTxId===txId);
|
|
1038
|
+
if(foundTxId!=null) return txIds;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
if(requiredStates!=null && !requiredStates.includes(this._state)) throw new Error("Swap state has changed before transactions were submitted!");
|
|
1043
|
+
|
|
1044
|
+
if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED) {
|
|
1045
|
+
const txIds = await this.wrapper._chain.sendSignedAndConfirm(parsedTxs, true, abortSignal, false);
|
|
1046
|
+
await this.waitTillClaimed(undefined, abortSignal);
|
|
1047
|
+
return txIds;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
throw new Error("Invalid swap state for transaction submission!");
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* @internal
|
|
1055
|
+
*/
|
|
1056
|
+
private async _buildClaimSmartChainTxAction(actionOptions?: {
|
|
1057
|
+
manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
|
|
1058
|
+
secret?: string
|
|
1059
|
+
}): Promise<SwapExecutionActionSignSmartChainTx<T>> {
|
|
1060
|
+
const signerAddress =
|
|
1061
|
+
await this.wrapper._getSignerAddress(actionOptions?.manualSettlementSmartChainSigner);
|
|
1062
|
+
|
|
1063
|
+
return {
|
|
1064
|
+
type: "SignSmartChainTransaction",
|
|
1065
|
+
name: "Settle manually",
|
|
1066
|
+
description: "Manually settle (claim) the swap on the destination smart chain",
|
|
1067
|
+
chain: this.chainIdentifier,
|
|
1068
|
+
txs: await this.prepareTransactions(this.txsClaim(actionOptions?.manualSettlementSmartChainSigner, actionOptions?.secret)),
|
|
1069
|
+
submitTransactions: async (txs: (T["SignedTXType"] | string)[], abortSignal?: AbortSignal, idempotent?: boolean) => {
|
|
1070
|
+
return this._submitExecutionTransactions(
|
|
1071
|
+
txs,
|
|
1072
|
+
abortSignal,
|
|
1073
|
+
[FromBTCLNAutoSwapState.CLAIM_COMMITED],
|
|
1074
|
+
idempotent
|
|
1075
|
+
);
|
|
1076
|
+
},
|
|
1077
|
+
requiredSigner: signerAddress ?? this._getInitiator()
|
|
1078
|
+
} as SwapExecutionActionSignSmartChainTx<T>;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
*
|
|
1083
|
+
* @param options.manualSettlementSmartChainSigner Optional smart chain signer to create a manual claim (settlement) transaction
|
|
1084
|
+
* @param options.maxWaitTillAutomaticSettlementSeconds Maximum time to wait for an automatic settlement after
|
|
1085
|
+
* the bitcoin transaction is confirmed (defaults to 60 seconds)
|
|
1086
|
+
* @param options.secret A swap secret to broadcast to watchtowers, generally only needed if the swap
|
|
1087
|
+
* was recovered from on-chain data, or the pre-image was generated outside the SDK
|
|
1088
|
+
*/
|
|
1089
|
+
async getExecutionAction(options?: {
|
|
1090
|
+
manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
|
|
1091
|
+
maxWaitTillAutomaticSettlementSeconds?: number,
|
|
1092
|
+
secret?: string
|
|
1093
|
+
}): Promise<
|
|
1094
|
+
SwapExecutionActionSendToAddress<true> |
|
|
1095
|
+
SwapExecutionActionWait<"LP" | "SETTLEMENT"> |
|
|
1096
|
+
SwapExecutionActionSignSmartChainTx<T> |
|
|
1097
|
+
undefined
|
|
1098
|
+
> {
|
|
1099
|
+
const executionStatus = await this._getExecutionStatus(options);
|
|
1100
|
+
return executionStatus.buildCurrentAction(options);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* @inheritDoc
|
|
1105
|
+
*/
|
|
1106
|
+
// TODO: Figure how we gonna trigger an LNURL-withdraw with the execution actions
|
|
1107
|
+
async getExecutionStatus(options?: {
|
|
1108
|
+
skipBuildingAction?: boolean,
|
|
1109
|
+
manualSettlementSmartChainSigner?: string | T["Signer"] | T["NativeSigner"],
|
|
1110
|
+
maxWaitTillAutomaticSettlementSeconds?: number,
|
|
1111
|
+
secret?: string
|
|
1112
|
+
}): Promise<{
|
|
1113
|
+
steps: [
|
|
1114
|
+
SwapExecutionStepPayment<"LIGHTNING">,
|
|
1115
|
+
SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
|
|
1116
|
+
],
|
|
1117
|
+
currentAction:
|
|
1118
|
+
SwapExecutionActionSendToAddress<true> |
|
|
1119
|
+
SwapExecutionActionWait<"LP" | "SETTLEMENT"> |
|
|
1120
|
+
SwapExecutionActionSignSmartChainTx<T> |
|
|
1121
|
+
undefined,
|
|
1122
|
+
stateInfo: SwapStateInfo<FromBTCLNAutoSwapState>
|
|
1123
|
+
}> {
|
|
1124
|
+
const executionStatus = await this._getExecutionStatus(options);
|
|
1125
|
+
return {
|
|
1126
|
+
steps: executionStatus.steps,
|
|
1127
|
+
currentAction: options?.skipBuildingAction ? undefined : await executionStatus.buildCurrentAction(options),
|
|
1128
|
+
stateInfo: this._getStateInfo(executionStatus.state)
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
/**
|
|
1133
|
+
* @inheritDoc
|
|
1134
|
+
*/
|
|
1135
|
+
async getExecutionSteps(options?: {
|
|
1136
|
+
maxWaitTillAutomaticSettlementSeconds?: number
|
|
1137
|
+
}): Promise<[
|
|
1138
|
+
SwapExecutionStepPayment<"LIGHTNING">,
|
|
1139
|
+
SwapExecutionStepSettlement<T["ChainId"], "awaiting_automatic" | "awaiting_manual">
|
|
1140
|
+
]> {
|
|
1141
|
+
return (await this._getExecutionStatus(options)).steps;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
|
|
1145
|
+
//////////////////////////////
|
|
1146
|
+
//// Payment
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Checks whether the LP received the LN payment
|
|
1150
|
+
*
|
|
1151
|
+
* @param save If the new swap state should be saved
|
|
1152
|
+
*
|
|
1153
|
+
* @internal
|
|
1154
|
+
*/
|
|
1155
|
+
async _checkIntermediaryPaymentReceived(save: boolean = true): Promise<boolean | null> {
|
|
1156
|
+
if(
|
|
1157
|
+
this._state===FromBTCLNAutoSwapState.PR_PAID ||
|
|
1158
|
+
this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED ||
|
|
1159
|
+
this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED ||
|
|
1160
|
+
this._state===FromBTCLNAutoSwapState.FAILED ||
|
|
1161
|
+
this._state===FromBTCLNAutoSwapState.EXPIRED
|
|
1162
|
+
) return true;
|
|
1163
|
+
if(this._state===FromBTCLNAutoSwapState.QUOTE_EXPIRED) return false;
|
|
1164
|
+
if(this.url==null) return false;
|
|
1165
|
+
|
|
1166
|
+
const paymentHash = this.getPaymentHash();
|
|
1167
|
+
if(paymentHash==null)
|
|
1168
|
+
throw new Error("Failed to check LP payment received, payment hash not known (probably recovered swap?)");
|
|
1169
|
+
|
|
1170
|
+
const resp = await this.wrapper._lpApi.getInvoiceStatus(this.url, paymentHash.toString("hex"));
|
|
1171
|
+
switch(resp.code) {
|
|
1172
|
+
case InvoiceStatusResponseCodes.PAID:
|
|
1173
|
+
const data = new (this.wrapper._swapDataDeserializer(this._contractVersion))(resp.data.data);
|
|
1174
|
+
if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) try {
|
|
1175
|
+
await this._saveRealSwapData(data, save);
|
|
1176
|
+
return true;
|
|
1177
|
+
} catch (e) {}
|
|
1178
|
+
return null;
|
|
1179
|
+
case InvoiceStatusResponseCodes.EXPIRED:
|
|
1180
|
+
this._state = FromBTCLNAutoSwapState.QUOTE_EXPIRED;
|
|
1181
|
+
this.initiated = true;
|
|
1182
|
+
if(save) await this._saveAndEmit();
|
|
1183
|
+
return false;
|
|
1184
|
+
default:
|
|
1185
|
+
return null;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
/**
|
|
1190
|
+
* Checks and overrides the swap data for this swap. This is used to set the swap data from
|
|
1191
|
+
* on-chain events.
|
|
1192
|
+
*
|
|
1193
|
+
* @param data Swap data of the escrow swap
|
|
1194
|
+
* @param save If the new data should be saved
|
|
1195
|
+
*
|
|
1196
|
+
* @internal
|
|
1197
|
+
*/
|
|
1198
|
+
async _saveRealSwapData(data: T["Data"], save?: boolean): Promise<boolean> {
|
|
1199
|
+
await this.checkIntermediaryReturnedData(data);
|
|
1200
|
+
if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1201
|
+
this._state = FromBTCLNAutoSwapState.PR_PAID;
|
|
1202
|
+
this._data = data;
|
|
1203
|
+
this.initiated = true;
|
|
1204
|
+
if(save) await this._saveAndEmit();
|
|
1205
|
+
return true;
|
|
1206
|
+
}
|
|
1207
|
+
return false;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
/**
|
|
1211
|
+
* Waits till a lightning network payment is received by the intermediary, and the intermediary
|
|
1212
|
+
* initiates the swap HTLC on the smart chain side. After the HTLC is initiated you can wait
|
|
1213
|
+
* for an automatic settlement by the watchtowers with the {@link waitTillClaimed} function,
|
|
1214
|
+
* or settle manually using the {@link claim} or {@link txsClaim} functions.
|
|
1215
|
+
*
|
|
1216
|
+
* If this swap is using an LNURL-withdraw link as input, it automatically posts the
|
|
1217
|
+
* generated invoice to the LNURL service to pay it.
|
|
1218
|
+
*
|
|
1219
|
+
* @remarks For internal use, rather use {@link waitForPayment} which properly waits till the LP also
|
|
1220
|
+
* offers a swap HTLC.
|
|
1221
|
+
*
|
|
1222
|
+
* @param abortSignal Abort signal to stop waiting for payment
|
|
1223
|
+
* @param checkIntervalSeconds How often to poll the intermediary for answer (default 5 seconds)
|
|
1224
|
+
*
|
|
1225
|
+
* @internal
|
|
1226
|
+
*/
|
|
1227
|
+
async _waitForLpPaymentReceived(checkIntervalSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
|
|
1228
|
+
checkIntervalSeconds ??= 5;
|
|
1229
|
+
if(this._state>=FromBTCLNAutoSwapState.PR_PAID) return true;
|
|
1230
|
+
if(
|
|
1231
|
+
this._state!==FromBTCLNAutoSwapState.PR_CREATED
|
|
1232
|
+
) throw new Error("Must be in PR_CREATED state!");
|
|
1233
|
+
|
|
1234
|
+
const abortController = new AbortController();
|
|
1235
|
+
if(abortSignal!=null) abortSignal.addEventListener("abort", () => abortController.abort(abortSignal.reason));
|
|
1236
|
+
|
|
1237
|
+
let save = false;
|
|
1238
|
+
|
|
1239
|
+
if(this.lnurl!=null && this.lnurlK1!=null && this.lnurlCallback!=null && !this.prPosted) {
|
|
1240
|
+
if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
|
|
1241
|
+
throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
|
|
1242
|
+
|
|
1243
|
+
LNURL.postInvoiceToLNURLWithdraw({k1: this.lnurlK1, callback: this.lnurlCallback}, this.pr).catch(e => {
|
|
1244
|
+
this.lnurlFailSignal.abort(e);
|
|
1245
|
+
});
|
|
1246
|
+
this.prPosted = true;
|
|
1247
|
+
save ||= true;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
if(!this.initiated) {
|
|
1251
|
+
this.initiated = true;
|
|
1252
|
+
save ||= true;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
if(save) await this._saveAndEmit();
|
|
1256
|
+
|
|
1257
|
+
let lnurlFailListener = () => abortController.abort(this.lnurlFailSignal.signal.reason);
|
|
1258
|
+
this.lnurlFailSignal.signal.addEventListener("abort", lnurlFailListener);
|
|
1259
|
+
this.lnurlFailSignal.signal.throwIfAborted();
|
|
1260
|
+
|
|
1261
|
+
const paymentHash = this.getPaymentHash();
|
|
1262
|
+
if(paymentHash==null)
|
|
1263
|
+
throw new Error("Swap payment hash not available, the swap was probably recovered!");
|
|
1264
|
+
|
|
1265
|
+
if(this.wrapper._messenger.warmup!=null) await this.wrapper._messenger.warmup().catch(e => {
|
|
1266
|
+
this.logger.warn("waitForPayment(): Failed to warmup messenger: ", e);
|
|
1267
|
+
});
|
|
1268
|
+
|
|
1269
|
+
if(this._state===FromBTCLNAutoSwapState.PR_CREATED) {
|
|
1270
|
+
const promises: Promise<boolean | undefined>[] = [
|
|
1271
|
+
this.waitTillState(FromBTCLNAutoSwapState.PR_PAID, "gte", abortController.signal).then(() => true)
|
|
1272
|
+
];
|
|
1273
|
+
if(this.url!=null) promises.push((async () => {
|
|
1274
|
+
let resp: InvoiceStatusResponse = {code: InvoiceStatusResponseCodes.PENDING, msg: ""};
|
|
1275
|
+
while(!abortController.signal.aborted && resp.code===InvoiceStatusResponseCodes.PENDING) {
|
|
1276
|
+
resp = await this.wrapper._lpApi.getInvoiceStatus(this.url!, paymentHash.toString("hex"));
|
|
1277
|
+
if(resp.code===InvoiceStatusResponseCodes.PENDING)
|
|
1278
|
+
await timeoutPromise(checkIntervalSeconds*1000, abortController.signal);
|
|
1279
|
+
}
|
|
1280
|
+
this.lnurlFailSignal.signal.removeEventListener("abort", lnurlFailListener);
|
|
1281
|
+
abortController.signal.throwIfAborted();
|
|
1282
|
+
|
|
1283
|
+
if(resp.code===InvoiceStatusResponseCodes.PAID) {
|
|
1284
|
+
const swapData = new (this.wrapper._swapDataDeserializer(this._contractVersion))(resp.data.data);
|
|
1285
|
+
return await this._saveRealSwapData(swapData, true);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1289
|
+
if(resp.code===InvoiceStatusResponseCodes.EXPIRED) {
|
|
1290
|
+
await this._saveAndEmit(FromBTCLNAutoSwapState.QUOTE_EXPIRED);
|
|
1291
|
+
}
|
|
1292
|
+
return false;
|
|
1293
|
+
}
|
|
1294
|
+
})());
|
|
1295
|
+
const paymentResult = await Promise.race(promises);
|
|
1296
|
+
abortController.abort();
|
|
1297
|
+
|
|
1298
|
+
if(!paymentResult) return false;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
return this._state>=FromBTCLNAutoSwapState.PR_PAID;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
|
|
1305
|
+
/**
|
|
1306
|
+
* Checks the data returned by the intermediary in the payment auth request
|
|
1307
|
+
*
|
|
1308
|
+
* @param data Parsed swap data as returned by the intermediary
|
|
1309
|
+
*
|
|
1310
|
+
* @throws {IntermediaryError} If the returned are not valid
|
|
1311
|
+
* @throws {Error} If the swap is already committed on-chain
|
|
1312
|
+
*
|
|
1313
|
+
* @private
|
|
1314
|
+
*/
|
|
1315
|
+
private async checkIntermediaryReturnedData(data: T["Data"]): Promise<void> {
|
|
1316
|
+
if (!data.isPayOut()) throw new IntermediaryError("Invalid not pay out");
|
|
1317
|
+
if (data.getType() !== ChainSwapType.HTLC) throw new IntermediaryError("Invalid swap type");
|
|
1318
|
+
if (!data.isOfferer(this.getSwapData().getOfferer())) throw new IntermediaryError("Invalid offerer used");
|
|
1319
|
+
if (!data.isClaimer(this._getInitiator())) throw new IntermediaryError("Invalid claimer used");
|
|
1320
|
+
if (!data.isToken(this.getSwapData().getToken())) throw new IntermediaryError("Invalid token used");
|
|
1321
|
+
if (data.getSecurityDeposit() !== this.getSwapData().getSecurityDeposit()) throw new IntermediaryError("Invalid security deposit!");
|
|
1322
|
+
if (data.getClaimerBounty() !== this.getSwapData().getClaimerBounty()) throw new IntermediaryError("Invalid security deposit!");
|
|
1323
|
+
if (data.getAmount() < this.getSwapData().getAmount()) throw new IntermediaryError("Invalid amount received!");
|
|
1324
|
+
if (data.getClaimHash() !== this.getSwapData().getClaimHash()) throw new IntermediaryError("Invalid payment hash used!");
|
|
1325
|
+
if (!data.isDepositToken(this.getSwapData().getDepositToken())) throw new IntermediaryError("Invalid deposit token used!");
|
|
1326
|
+
if (data.hasSuccessAction()) throw new IntermediaryError("Invalid has success action");
|
|
1327
|
+
|
|
1328
|
+
if (await this.wrapper._contract(this._contractVersion).isExpired(this._getInitiator(), data)) throw new IntermediaryError("Not enough time to claim!");
|
|
1329
|
+
if (this.wrapper._getHtlcTimeout(data) <= (Date.now()/1000)) throw new IntermediaryError("HTLC expires too soon!");
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
/**
|
|
1333
|
+
* Waits till a lightning network payment is received by the intermediary, and the intermediary
|
|
1334
|
+
* initiates the swap HTLC on the smart chain side. After the HTLC is initiated you can wait
|
|
1335
|
+
* for an automatic settlement by the watchtowers with the {@link waitTillClaimed} function,
|
|
1336
|
+
* or settle manually using the {@link claim} or {@link txsClaim} functions.
|
|
1337
|
+
*
|
|
1338
|
+
* If this swap is using an LNURL-withdraw link as input, it automatically posts the
|
|
1339
|
+
* generated invoice to the LNURL service to pay it.
|
|
1340
|
+
*
|
|
1341
|
+
* @param onPaymentReceived Callback as for when the LP reports having received the ln payment
|
|
1342
|
+
* @param abortSignal Abort signal to stop waiting for payment
|
|
1343
|
+
* @param checkIntervalSeconds How often to poll the intermediary for answer (default 5 seconds)
|
|
1344
|
+
*/
|
|
1345
|
+
async waitForPayment(onPaymentReceived?: (txId: string) => void, checkIntervalSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
|
|
1346
|
+
checkIntervalSeconds ??= 5;
|
|
1347
|
+
if(this._state===FromBTCLNAutoSwapState.PR_PAID) {
|
|
1348
|
+
await this.waitTillCommited(checkIntervalSeconds, abortSignal);
|
|
1349
|
+
}
|
|
1350
|
+
if(this._state>=FromBTCLNAutoSwapState.CLAIM_COMMITED) return true;
|
|
1351
|
+
|
|
1352
|
+
const success = await this._waitForLpPaymentReceived(checkIntervalSeconds, abortSignal);
|
|
1353
|
+
if(!success) return false;
|
|
1354
|
+
|
|
1355
|
+
if(onPaymentReceived!=null) onPaymentReceived(this.getInputTxId()!);
|
|
1356
|
+
|
|
1357
|
+
if((this._state as FromBTCLNAutoSwapState)===FromBTCLNAutoSwapState.PR_PAID) {
|
|
1358
|
+
await this.waitTillCommited(checkIntervalSeconds, abortSignal);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
return this._state>=FromBTCLNAutoSwapState.CLAIM_COMMITED;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
|
|
1365
|
+
//////////////////////////////
|
|
1366
|
+
//// Commit
|
|
1367
|
+
|
|
1368
|
+
/**
|
|
1369
|
+
* Waits till the intermediary (LP) initiates the swap HTLC escrow on the destination smart chain side
|
|
1370
|
+
*
|
|
1371
|
+
* @param checkIntervalSeconds How often to check via a polling watchdog
|
|
1372
|
+
* @param abortSignal Abort signal
|
|
1373
|
+
*
|
|
1374
|
+
* @internal
|
|
1375
|
+
*/
|
|
1376
|
+
protected async waitTillCommited(checkIntervalSeconds?: number, abortSignal?: AbortSignal): Promise<void> {
|
|
1377
|
+
if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED || this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED) return Promise.resolve();
|
|
1378
|
+
if(this._state!==FromBTCLNAutoSwapState.PR_PAID) throw new Error("Invalid state");
|
|
1379
|
+
|
|
1380
|
+
const abortController = extendAbortController(abortSignal);
|
|
1381
|
+
let result: SwapCommitState | number | null;
|
|
1382
|
+
try {
|
|
1383
|
+
result = await Promise.race([
|
|
1384
|
+
this.watchdogWaitTillCommited(checkIntervalSeconds, abortController.signal),
|
|
1385
|
+
this.waitTillState(FromBTCLNAutoSwapState.CLAIM_COMMITED, "gte", abortController.signal).then(() => 0)
|
|
1386
|
+
]);
|
|
1387
|
+
abortController.abort();
|
|
1388
|
+
} catch (e) {
|
|
1389
|
+
abortController.abort();
|
|
1390
|
+
throw e;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
if(result===null) {
|
|
1394
|
+
this.logger.debug("waitTillCommited(): Resolved from watchdog - HTLC expired");
|
|
1395
|
+
if(
|
|
1396
|
+
this._state===FromBTCLNAutoSwapState.PR_PAID
|
|
1397
|
+
) {
|
|
1398
|
+
await this._saveAndEmit(FromBTCLNAutoSwapState.EXPIRED);
|
|
1399
|
+
}
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
if(
|
|
1404
|
+
this._state===FromBTCLNAutoSwapState.PR_PAID
|
|
1405
|
+
) {
|
|
1406
|
+
if(typeof(result)==="object" && (result as any).getInitTxId!=null && this._commitTxId==null)
|
|
1407
|
+
this._commitTxId = await (result as any).getInitTxId();
|
|
1408
|
+
this._commitedAt ??= Date.now();
|
|
1409
|
+
await this._saveAndEmit(FromBTCLNAutoSwapState.CLAIM_COMMITED);
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
if(result===0) {
|
|
1413
|
+
this.logger.debug("waitTillCommited(): Resolved from state changed");
|
|
1414
|
+
} else if(result!=null) {
|
|
1415
|
+
this.logger.debug("waitTillCommited(): Resolved from watchdog - commited");
|
|
1416
|
+
if(this.secret!=null) await this._broadcastSecret().catch(e => {
|
|
1417
|
+
this.logger.error("waitTillCommited(): Error broadcasting swap secret: ", e);
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
|
|
1423
|
+
//////////////////////////////
|
|
1424
|
+
//// Claim
|
|
1425
|
+
|
|
1426
|
+
/**
|
|
1427
|
+
* @inheritDoc
|
|
1428
|
+
*
|
|
1429
|
+
* @param _signer Optional signer address to use for claiming the swap, can also be different from the initializer
|
|
1430
|
+
* @param secret A swap secret to use for the claim transaction, generally only needed if the swap
|
|
1431
|
+
* was recovered from on-chain data, or the pre-image was generated outside the SDK
|
|
1432
|
+
*
|
|
1433
|
+
* @throws {Error} If in invalid state (must be {@link FromBTCLNAutoSwapState.CLAIM_COMMITED})
|
|
1434
|
+
*/
|
|
1435
|
+
async txsClaim(_signer?: string | T["Signer"] | T["NativeSigner"], secret?: string): Promise<T["TX"][]> {
|
|
1436
|
+
let address: string | undefined = undefined;
|
|
1437
|
+
if(_signer!=null) {
|
|
1438
|
+
if (typeof (_signer) === "string") {
|
|
1439
|
+
address = _signer;
|
|
1440
|
+
} else if (isAbstractSigner(_signer)) {
|
|
1441
|
+
address = _signer.getAddress();
|
|
1442
|
+
} else {
|
|
1443
|
+
address = (await this.wrapper._chain.wrapSigner(_signer)).getAddress();
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
if(this._state!==FromBTCLNAutoSwapState.CLAIM_COMMITED) throw new Error("Must be in CLAIM_COMMITED state!");
|
|
1447
|
+
if(this._data==null) throw new Error("Unknown data, wrong state?");
|
|
1448
|
+
|
|
1449
|
+
const useSecret = secret ?? this.secret;
|
|
1450
|
+
if(useSecret==null)
|
|
1451
|
+
throw new Error("Swap secret pre-image not known and not provided, please provide the swap secret pre-image as an argument");
|
|
1452
|
+
if(!this.isValidSecretPreimage(useSecret))
|
|
1453
|
+
throw new Error("Invalid swap secret pre-image provided!");
|
|
1454
|
+
|
|
1455
|
+
return await this._contract.txsClaimWithSecret(
|
|
1456
|
+
address ?? this._getInitiator(),
|
|
1457
|
+
this._data, useSecret, true, true
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
/**
|
|
1462
|
+
* @inheritDoc
|
|
1463
|
+
*
|
|
1464
|
+
* @param _signer Signer to sign the transactions with, can also be different to the initializer
|
|
1465
|
+
* @param abortSignal Abort signal to stop waiting for transaction confirmation
|
|
1466
|
+
* @param onBeforeTxSent
|
|
1467
|
+
* @param secret A swap secret to use for the claim transaction, generally only needed if the swap
|
|
1468
|
+
* was recovered from on-chain data, or the pre-image was generated outside the SDK
|
|
1469
|
+
*/
|
|
1470
|
+
async claim(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, onBeforeTxSent?: (txId: string) => void, secret?: string): Promise<string> {
|
|
1471
|
+
const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
|
|
1472
|
+
let txCount = 0;
|
|
1473
|
+
const txs = await this.txsClaim(_signer, secret);
|
|
1474
|
+
const result = await this.wrapper._chain.sendAndConfirm(
|
|
1475
|
+
signer, txs, true, abortSignal, undefined, (txId: string) => {
|
|
1476
|
+
txCount++;
|
|
1477
|
+
if(onBeforeTxSent!=null && txCount===1) onBeforeTxSent(txId);
|
|
1478
|
+
return Promise.resolve();
|
|
1479
|
+
}
|
|
1480
|
+
);
|
|
1481
|
+
|
|
1482
|
+
this._claimTxId = result[0];
|
|
1483
|
+
if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED || this._state===FromBTCLNAutoSwapState.EXPIRED || this._state===FromBTCLNAutoSwapState.FAILED) {
|
|
1484
|
+
await this._saveAndEmit(FromBTCLNAutoSwapState.CLAIM_CLAIMED);
|
|
1485
|
+
}
|
|
1486
|
+
return result[0];
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
/**
|
|
1490
|
+
* Waits till the swap is successfully settled (claimed), should be called after sending the claim (settlement)
|
|
1491
|
+
* transactions manually to wait till the SDK processes the settlement and updates the swap state accordingly.
|
|
1492
|
+
*
|
|
1493
|
+
* @param maxWaitTimeSeconds Maximum time in seconds to wait for the swap to be settled
|
|
1494
|
+
* @param abortSignal AbortSignal
|
|
1495
|
+
* @param secret A swap secret to broadcast to watchtowers, generally only needed if the swap
|
|
1496
|
+
* was recovered from on-chain data, or the pre-image was generated outside the SDK
|
|
1497
|
+
* @param pollIntervalSeconds How often to poll via the watchdog
|
|
1498
|
+
*
|
|
1499
|
+
* @throws {Error} If swap is in invalid state (must be {@link FromBTCLNAutoSwapState.CLAIM_COMMITED})
|
|
1500
|
+
* @throws {Error} If the LP refunded sooner than we were able to claim
|
|
1501
|
+
* @returns {boolean} whether the swap was claimed in time or not
|
|
1502
|
+
*/
|
|
1503
|
+
async waitTillClaimed(maxWaitTimeSeconds?: number, abortSignal?: AbortSignal, secret?: string, pollIntervalSeconds?: number): Promise<boolean> {
|
|
1504
|
+
if(this._state===FromBTCLNAutoSwapState.CLAIM_CLAIMED) return Promise.resolve(true);
|
|
1505
|
+
if(this._state!==FromBTCLNAutoSwapState.CLAIM_COMMITED) throw new Error("Invalid state (not CLAIM_COMMITED)");
|
|
1506
|
+
|
|
1507
|
+
if(secret!=null) {
|
|
1508
|
+
if(!this.isValidSecretPreimage(secret))
|
|
1509
|
+
throw new Error("Invalid swap secret pre-image provided!");
|
|
1510
|
+
this.secret = secret;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
const abortController = new AbortController();
|
|
1514
|
+
if(abortSignal!=null) abortSignal.addEventListener("abort", () => abortController.abort(abortSignal.reason));
|
|
1515
|
+
let timedOut: boolean = false;
|
|
1516
|
+
if(maxWaitTimeSeconds!=null) {
|
|
1517
|
+
const timeout = setTimeout(() => {
|
|
1518
|
+
timedOut = true;
|
|
1519
|
+
abortController.abort();
|
|
1520
|
+
}, maxWaitTimeSeconds * 1000);
|
|
1521
|
+
abortController.signal.addEventListener("abort", () => clearTimeout(timeout));
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
let res: 0 | 1 | SwapCommitState;
|
|
1525
|
+
try {
|
|
1526
|
+
res = await Promise.race([
|
|
1527
|
+
this.watchdogWaitTillResult(pollIntervalSeconds, abortController.signal),
|
|
1528
|
+
this.waitTillState(FromBTCLNAutoSwapState.CLAIM_CLAIMED, "eq", abortController.signal).then(() => 0 as const),
|
|
1529
|
+
this.waitTillState(FromBTCLNAutoSwapState.EXPIRED, "eq", abortController.signal).then(() => 1 as const),
|
|
1530
|
+
]);
|
|
1531
|
+
abortController.abort();
|
|
1532
|
+
} catch (e) {
|
|
1533
|
+
abortController.abort();
|
|
1534
|
+
if(timedOut) return false;
|
|
1535
|
+
throw e;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
if(res===0) {
|
|
1539
|
+
this.logger.debug("waitTillClaimed(): Resolved from state change (CLAIM_CLAIMED)");
|
|
1540
|
+
return true;
|
|
1541
|
+
}
|
|
1542
|
+
if(res===1) {
|
|
1543
|
+
this.logger.debug("waitTillClaimed(): Resolved from state change (EXPIRED)");
|
|
1544
|
+
throw new Error("Swap expired during claiming");
|
|
1545
|
+
}
|
|
1546
|
+
this.logger.debug("waitTillClaimed(): Resolved from watchdog");
|
|
1547
|
+
|
|
1548
|
+
if(res?.type===SwapCommitStateType.PAID) {
|
|
1549
|
+
if((this._state as FromBTCLNAutoSwapState)!==FromBTCLNAutoSwapState.CLAIM_CLAIMED) {
|
|
1550
|
+
this._claimTxId = await res.getClaimTxId();
|
|
1551
|
+
await this._saveAndEmit(FromBTCLNAutoSwapState.CLAIM_CLAIMED);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
if(res?.type===SwapCommitStateType.NOT_COMMITED || res?.type===SwapCommitStateType.EXPIRED) {
|
|
1555
|
+
if(
|
|
1556
|
+
(this._state as FromBTCLNAutoSwapState)!==FromBTCLNAutoSwapState.CLAIM_CLAIMED &&
|
|
1557
|
+
(this._state as FromBTCLNAutoSwapState)!==FromBTCLNAutoSwapState.FAILED
|
|
1558
|
+
) {
|
|
1559
|
+
await this._saveAndEmit(FromBTCLNAutoSwapState.FAILED);
|
|
1560
|
+
}
|
|
1561
|
+
throw new Error("Swap expired during claiming");
|
|
1562
|
+
}
|
|
1563
|
+
return true;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
|
|
1567
|
+
//////////////////////////////
|
|
1568
|
+
//// LNURL
|
|
1569
|
+
|
|
1570
|
+
/**
|
|
1571
|
+
* Whether this swap uses an LNURL-withdraw link
|
|
1572
|
+
*/
|
|
1573
|
+
isLNURL(): boolean {
|
|
1574
|
+
return this.lnurl!=null;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
/**
|
|
1578
|
+
* Gets the used LNURL or `null` if this is not an LNURL-withdraw swap
|
|
1579
|
+
*/
|
|
1580
|
+
getLNURL(): string | null {
|
|
1581
|
+
return this.lnurl ?? null;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
/**
|
|
1585
|
+
* Pay the generated lightning network invoice with an LNURL-withdraw link, this
|
|
1586
|
+
* is useful when you want to display a lightning payment QR code and also want to
|
|
1587
|
+
* allow payments using LNURL-withdraw NFC cards.
|
|
1588
|
+
*
|
|
1589
|
+
* Note that the swap needs to be created **without** an LNURL to begin with for this function
|
|
1590
|
+
* to work. If this swap is already using an LNURL-withdraw link, this function throws.
|
|
1591
|
+
*/
|
|
1592
|
+
async settleWithLNURLWithdraw(lnurl: string | LNURLWithdraw): Promise<void> {
|
|
1593
|
+
if(
|
|
1594
|
+
this._state!==FromBTCLNAutoSwapState.PR_CREATED &&
|
|
1595
|
+
this._state!==FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED
|
|
1596
|
+
) throw new Error("Must be in PR_CREATED state!");
|
|
1597
|
+
|
|
1598
|
+
if(this.lnurl!=null) throw new Error("Cannot settle LNURL-withdraw swap with different LNURL");
|
|
1599
|
+
let lnurlParams: LNURLWithdrawParamsWithUrl;
|
|
1600
|
+
if(typeof(lnurl)==="string") {
|
|
1601
|
+
const parsedLNURL = await LNURL.getLNURL(lnurl);
|
|
1602
|
+
if(parsedLNURL==null || parsedLNURL.tag!=="withdrawRequest")
|
|
1603
|
+
throw new UserError("Invalid LNURL-withdraw to settle the swap");
|
|
1604
|
+
lnurlParams = parsedLNURL;
|
|
1605
|
+
} else {
|
|
1606
|
+
lnurlParams = lnurl.params;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
if(this.pr==null || !this.pr.toLowerCase().startsWith("ln"))
|
|
1610
|
+
throw new Error("Input lightning network invoice not available, the swap was probably recovered!");
|
|
1611
|
+
|
|
1612
|
+
LNURL.useLNURLWithdraw(lnurlParams, this.pr).catch(e => this.lnurlFailSignal.abort(e));
|
|
1613
|
+
this.lnurl = lnurlParams.url;
|
|
1614
|
+
this.lnurlCallback = lnurlParams.callback;
|
|
1615
|
+
this.lnurlK1 = lnurlParams.k1;
|
|
1616
|
+
this.prPosted = true;
|
|
1617
|
+
await this._saveAndEmit();
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
|
|
1621
|
+
//////////////////////////////
|
|
1622
|
+
//// Storage
|
|
1623
|
+
|
|
1624
|
+
/**
|
|
1625
|
+
* @inheritDoc
|
|
1626
|
+
*/
|
|
1627
|
+
serialize(): any {
|
|
1628
|
+
return {
|
|
1629
|
+
...super.serialize(),
|
|
1630
|
+
data: this._data==null ? null : this._data.serialize(),
|
|
1631
|
+
commitTxId: this._commitTxId,
|
|
1632
|
+
claimTxId: this._claimTxId,
|
|
1633
|
+
commitedAt: this._commitedAt,
|
|
1634
|
+
btcAmountSwap: this.btcAmountSwap==null ? null : this.btcAmountSwap.toString(10),
|
|
1635
|
+
btcAmountGas: this.btcAmountGas==null ? null : this.btcAmountGas.toString(10),
|
|
1636
|
+
gasSwapFeeBtc: this.gasSwapFeeBtc==null ? null : this.gasSwapFeeBtc.toString(10),
|
|
1637
|
+
gasSwapFee: this.gasSwapFee==null ? null : this.gasSwapFee.toString(10),
|
|
1638
|
+
gasPricingInfo: serializePriceInfoType(this.gasPricingInfo),
|
|
1639
|
+
pr: this.pr,
|
|
1640
|
+
secret: this.secret,
|
|
1641
|
+
lnurl: this.lnurl,
|
|
1642
|
+
lnurlK1: this.lnurlK1,
|
|
1643
|
+
lnurlCallback: this.lnurlCallback,
|
|
1644
|
+
prPosted: this.prPosted,
|
|
1645
|
+
initialSwapData: this.initialSwapData.serialize(),
|
|
1646
|
+
usesClaimHashAsId: this.usesClaimHashAsId
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
|
|
1651
|
+
//////////////////////////////
|
|
1652
|
+
//// Swap ticks & sync
|
|
1653
|
+
|
|
1654
|
+
/**
|
|
1655
|
+
* Checks the swap's state on-chain and compares it to its internal state, updates/changes it according to on-chain
|
|
1656
|
+
* data
|
|
1657
|
+
*
|
|
1658
|
+
* @private
|
|
1659
|
+
*/
|
|
1660
|
+
private async syncStateFromChain(quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState): Promise<boolean> {
|
|
1661
|
+
if(
|
|
1662
|
+
this._state===FromBTCLNAutoSwapState.PR_PAID ||
|
|
1663
|
+
this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED ||
|
|
1664
|
+
this._state===FromBTCLNAutoSwapState.EXPIRED
|
|
1665
|
+
) {
|
|
1666
|
+
//Check for expiry before the getCommitStatus to prevent race conditions
|
|
1667
|
+
let quoteExpired: boolean = false;
|
|
1668
|
+
if(this._state===FromBTCLNAutoSwapState.PR_PAID) {
|
|
1669
|
+
quoteExpired = quoteDefinitelyExpired ?? await this._verifyQuoteDefinitelyExpired();
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
//Check if it's already successfully paid
|
|
1673
|
+
commitStatus ??= await this._contract.getCommitStatus(this._getInitiator(), this._data!);
|
|
1674
|
+
if(commitStatus!=null && await this._forciblySetOnchainState(commitStatus)) return true;
|
|
1675
|
+
|
|
1676
|
+
if(this._state===FromBTCLNAutoSwapState.PR_PAID) {
|
|
1677
|
+
if(quoteExpired) {
|
|
1678
|
+
this._state = FromBTCLNAutoSwapState.QUOTE_EXPIRED;
|
|
1679
|
+
return true;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
return false;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
/**
|
|
1688
|
+
* @inheritDoc
|
|
1689
|
+
* @internal
|
|
1690
|
+
*/
|
|
1691
|
+
_shouldFetchOnchainState(): boolean {
|
|
1692
|
+
return this._state===FromBTCLNAutoSwapState.PR_PAID || this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED || this._state===FromBTCLNAutoSwapState.EXPIRED;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
/**
|
|
1696
|
+
* @inheritDoc
|
|
1697
|
+
* @internal
|
|
1698
|
+
*/
|
|
1699
|
+
_shouldFetchExpiryStatus(): boolean {
|
|
1700
|
+
return this._state===FromBTCLNAutoSwapState.PR_PAID;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
/**
|
|
1704
|
+
* @inheritDoc
|
|
1705
|
+
* @internal
|
|
1706
|
+
*/
|
|
1707
|
+
_shouldCheckIntermediary(): boolean {
|
|
1708
|
+
return this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
/**
|
|
1712
|
+
* @inheritDoc
|
|
1713
|
+
* @internal
|
|
1714
|
+
*/
|
|
1715
|
+
async _sync(save?: boolean, quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState, skipLpCheck?: boolean): Promise<boolean> {
|
|
1716
|
+
let changed = false;
|
|
1717
|
+
|
|
1718
|
+
if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1719
|
+
if(this._state!==FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED && this.getQuoteExpiry()<Date.now()) {
|
|
1720
|
+
this._state = FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED;
|
|
1721
|
+
changed ||= true;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
if(!skipLpCheck) try {
|
|
1725
|
+
const result = await this._checkIntermediaryPaymentReceived(false);
|
|
1726
|
+
if (result !== null) changed ||= true;
|
|
1727
|
+
} catch(e) {
|
|
1728
|
+
this.logger.error("_sync(): Failed to synchronize swap, error: ", e);
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
if(this._state===FromBTCLNAutoSwapState.PR_CREATED || this._state===FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1732
|
+
if(await this._verifyQuoteDefinitelyExpired()) {
|
|
1733
|
+
this._state = FromBTCLNAutoSwapState.QUOTE_EXPIRED;
|
|
1734
|
+
changed ||= true;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
if(await this.syncStateFromChain(quoteDefinitelyExpired, commitStatus)) changed = true;
|
|
1740
|
+
|
|
1741
|
+
if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED) {
|
|
1742
|
+
const expired = await this._contract.isExpired(this._getInitiator(), this._data!);
|
|
1743
|
+
if(expired) {
|
|
1744
|
+
this._state = FromBTCLNAutoSwapState.EXPIRED;
|
|
1745
|
+
changed = true;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
if(save && changed) await this._saveAndEmit();
|
|
1750
|
+
|
|
1751
|
+
if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED && this.secret!=null) await this._broadcastSecret().catch(e => {
|
|
1752
|
+
this.logger.error("_sync(): Error when broadcasting swap secret: ", e);
|
|
1753
|
+
});
|
|
1754
|
+
|
|
1755
|
+
return changed;
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
/**
|
|
1759
|
+
* @inheritDoc
|
|
1760
|
+
* @internal
|
|
1761
|
+
*/
|
|
1762
|
+
async _forciblySetOnchainState(commitStatus: SwapCommitState): Promise<boolean> {
|
|
1763
|
+
switch(commitStatus?.type) {
|
|
1764
|
+
case SwapCommitStateType.PAID:
|
|
1765
|
+
if(this._commitTxId==null && (commitStatus as any).getInitTxId!=null) this._commitTxId = await (commitStatus as any).getInitTxId();
|
|
1766
|
+
if(this._claimTxId==null) this._claimTxId = await commitStatus.getClaimTxId();
|
|
1767
|
+
if(this.secret==null || this.pr==null) this._setSwapSecret(await commitStatus.getClaimResult());
|
|
1768
|
+
this._state = FromBTCLNAutoSwapState.CLAIM_CLAIMED;
|
|
1769
|
+
return true;
|
|
1770
|
+
case SwapCommitStateType.NOT_COMMITED:
|
|
1771
|
+
let changed: boolean = false;
|
|
1772
|
+
if(this._commitTxId==null && (commitStatus as any).getInitTxId!=null) {
|
|
1773
|
+
this._commitTxId = await (commitStatus as any).getInitTxId();
|
|
1774
|
+
changed = true;
|
|
1775
|
+
}
|
|
1776
|
+
if(this._refundTxId==null && commitStatus.getRefundTxId!=null) {
|
|
1777
|
+
this._refundTxId = await commitStatus.getRefundTxId();
|
|
1778
|
+
changed = true;
|
|
1779
|
+
}
|
|
1780
|
+
if(this._refundTxId!=null) {
|
|
1781
|
+
this._state = FromBTCLNAutoSwapState.FAILED;
|
|
1782
|
+
changed = true;
|
|
1783
|
+
}
|
|
1784
|
+
return changed;
|
|
1785
|
+
case SwapCommitStateType.EXPIRED:
|
|
1786
|
+
if(this._commitTxId==null && (commitStatus as any).getInitTxId!=null) this._commitTxId = await (commitStatus as any).getInitTxId();
|
|
1787
|
+
if(this._refundTxId==null && commitStatus.getRefundTxId!=null) this._refundTxId = await commitStatus.getRefundTxId();
|
|
1788
|
+
this._state = this._refundTxId==null ? FromBTCLNAutoSwapState.QUOTE_EXPIRED : FromBTCLNAutoSwapState.FAILED;
|
|
1789
|
+
return true;
|
|
1790
|
+
case SwapCommitStateType.COMMITED:
|
|
1791
|
+
let save: boolean = false;
|
|
1792
|
+
if(this._commitTxId==null && (commitStatus as any).getInitTxId!=null) {
|
|
1793
|
+
this._commitTxId = await (commitStatus as any).getInitTxId();
|
|
1794
|
+
save = true;
|
|
1795
|
+
}
|
|
1796
|
+
if(this._state!==FromBTCLNAutoSwapState.CLAIM_COMMITED && this._state!==FromBTCLNAutoSwapState.EXPIRED) {
|
|
1797
|
+
this._commitedAt ??= Date.now();
|
|
1798
|
+
this._state = FromBTCLNAutoSwapState.CLAIM_COMMITED;
|
|
1799
|
+
save = true;
|
|
1800
|
+
}
|
|
1801
|
+
return save;
|
|
1802
|
+
}
|
|
1803
|
+
return false;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
/**
|
|
1807
|
+
* Broadcasts the swap secret to the underlying data propagation layer (e.g. Nostr by default)
|
|
1808
|
+
*
|
|
1809
|
+
* @param noCheckExpiry Whether a swap expiration check should be skipped broadcasting
|
|
1810
|
+
* @param secret An optional secret pre-image for the swap to broadcast
|
|
1811
|
+
*
|
|
1812
|
+
* @internal
|
|
1813
|
+
*/
|
|
1814
|
+
async _broadcastSecret(noCheckExpiry?: boolean, secret?: string): Promise<void> {
|
|
1815
|
+
if(this._state!==FromBTCLNAutoSwapState.CLAIM_COMMITED) throw new Error("Must be in CLAIM_COMMITED state to broadcast swap secret!");
|
|
1816
|
+
if(this._data==null) throw new Error("Unknown data, wrong state?");
|
|
1817
|
+
|
|
1818
|
+
const useSecret = secret ?? this.secret;
|
|
1819
|
+
if(useSecret==null)
|
|
1820
|
+
throw new Error("Swap secret pre-image not known and not provided, please provide the swap secret pre-image as an argument");
|
|
1821
|
+
if(!this.isValidSecretPreimage(useSecret))
|
|
1822
|
+
throw new Error("Invalid swap secret pre-image provided!");
|
|
1823
|
+
|
|
1824
|
+
if(!noCheckExpiry) {
|
|
1825
|
+
if(await this._contract.isExpired(this._getInitiator(), this._data)) throw new Error("On-chain HTLC already expired!");
|
|
1826
|
+
}
|
|
1827
|
+
await this.wrapper._messenger.broadcast(new SwapClaimWitnessMessage(this._data, useSecret));
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
/**
|
|
1831
|
+
* @inheritDoc
|
|
1832
|
+
* @internal
|
|
1833
|
+
*/
|
|
1834
|
+
async _tick(save?: boolean): Promise<boolean> {
|
|
1835
|
+
switch(this._state) {
|
|
1836
|
+
case FromBTCLNAutoSwapState.PR_CREATED:
|
|
1837
|
+
if(this.getQuoteExpiry() < Date.now()) {
|
|
1838
|
+
this._state = FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED;
|
|
1839
|
+
if(save) await this._saveAndEmit();
|
|
1840
|
+
return true;
|
|
1841
|
+
}
|
|
1842
|
+
break;
|
|
1843
|
+
case FromBTCLNAutoSwapState.QUOTE_SOFT_EXPIRED:
|
|
1844
|
+
if(this.getDefinitiveExpiryTime() < Date.now()) {
|
|
1845
|
+
this._state = FromBTCLNAutoSwapState.QUOTE_EXPIRED;
|
|
1846
|
+
if(save) await this._saveAndEmit();
|
|
1847
|
+
return true;
|
|
1848
|
+
}
|
|
1849
|
+
break;
|
|
1850
|
+
case FromBTCLNAutoSwapState.PR_PAID:
|
|
1851
|
+
case FromBTCLNAutoSwapState.CLAIM_COMMITED:
|
|
1852
|
+
const expired = await this._contract.isExpired(this._getInitiator(), this._data!);
|
|
1853
|
+
if(expired) {
|
|
1854
|
+
this._state = FromBTCLNAutoSwapState.EXPIRED;
|
|
1855
|
+
if(save) await this._saveAndEmit();
|
|
1856
|
+
return true;
|
|
1857
|
+
}
|
|
1858
|
+
if(this._state===FromBTCLNAutoSwapState.CLAIM_COMMITED) {
|
|
1859
|
+
//Broadcast the secret over the provided messenger channel
|
|
1860
|
+
if(this.broadcastTickCounter===0 && this.secret!=null) await this._broadcastSecret(true).catch(e => {
|
|
1861
|
+
this.logger.warn("_tick(): Error when broadcasting swap secret: ", e);
|
|
1862
|
+
});
|
|
1863
|
+
this.broadcastTickCounter = (this.broadcastTickCounter + 1) % 3; //Broadcast every 3rd tick
|
|
1864
|
+
}
|
|
1865
|
+
break;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
return false;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
/**
|
|
1872
|
+
* Forcibly sets the swap secret pre-image from on-chain data
|
|
1873
|
+
*
|
|
1874
|
+
* @internal
|
|
1875
|
+
*/
|
|
1876
|
+
_setSwapSecret(secret: string) {
|
|
1877
|
+
this.secret = secret;
|
|
1878
|
+
if(this.pr==null) {
|
|
1879
|
+
this.pr = Buffer.from(sha256(Buffer.from(secret, "hex"))).toString("hex");
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
}
|