@atomiqlabs/sdk 8.1.8 → 8.3.5
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/dist/bitcoin/coinselect2/utils.d.ts +6 -0
- package/dist/bitcoin/wallet/BitcoinWallet.d.ts +41 -5
- package/dist/bitcoin/wallet/BitcoinWallet.js +36 -1
- package/dist/bitcoin/wallet/IBitcoinWallet.d.ts +52 -2
- package/dist/bitcoin/wallet/IBitcoinWallet.js +2 -1
- package/dist/bitcoin/wallet/SingleAddressBitcoinWallet.d.ts +42 -7
- package/dist/bitcoin/wallet/SingleAddressBitcoinWallet.js +36 -1
- package/dist/enums/FeeType.d.ts +8 -1
- package/dist/enums/FeeType.js +8 -1
- package/dist/enums/SwapAmountType.d.ts +7 -0
- package/dist/enums/SwapAmountType.js +7 -0
- package/dist/enums/SwapDirection.d.ts +7 -0
- package/dist/enums/SwapDirection.js +7 -0
- package/dist/enums/SwapType.d.ts +62 -1
- package/dist/enums/SwapType.js +62 -1
- package/dist/errors/IntermediaryError.d.ts +4 -0
- package/dist/errors/IntermediaryError.js +1 -0
- package/dist/errors/RequestError.d.ts +15 -1
- package/dist/errors/RequestError.js +8 -0
- package/dist/errors/UserError.d.ts +1 -0
- package/dist/errors/UserError.js +1 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +7 -6
- package/dist/intermediaries/Intermediary.d.ts +61 -14
- package/dist/intermediaries/Intermediary.js +38 -11
- package/dist/intermediaries/IntermediaryDiscovery.d.ts +62 -29
- package/dist/intermediaries/IntermediaryDiscovery.js +39 -24
- package/dist/prices/RedundantSwapPrice.d.ts +26 -5
- package/dist/prices/RedundantSwapPrice.js +22 -2
- package/dist/prices/SingleSwapPrice.d.ts +10 -7
- package/dist/prices/SingleSwapPrice.js +11 -8
- package/dist/prices/SwapPriceWithChain.d.ts +56 -19
- package/dist/prices/SwapPriceWithChain.js +62 -25
- package/dist/prices/abstract/IPriceProvider.d.ts +4 -4
- package/dist/prices/abstract/IPriceProvider.js +1 -1
- package/dist/prices/abstract/ISwapPrice.d.ts +95 -46
- package/dist/prices/abstract/ISwapPrice.js +104 -56
- package/dist/prices/providers/BinancePriceProvider.d.ts +8 -1
- package/dist/prices/providers/BinancePriceProvider.js +8 -1
- package/dist/prices/providers/CoinGeckoPriceProvider.d.ts +7 -1
- package/dist/prices/providers/CoinGeckoPriceProvider.js +7 -1
- package/dist/prices/providers/CoinPaprikaPriceProvider.d.ts +7 -1
- package/dist/prices/providers/CoinPaprikaPriceProvider.js +7 -1
- package/dist/prices/providers/CustomPriceProvider.d.ts +12 -1
- package/dist/prices/providers/CustomPriceProvider.js +12 -1
- package/dist/prices/providers/KrakenPriceProvider.d.ts +10 -1
- package/dist/prices/providers/KrakenPriceProvider.js +10 -1
- package/dist/prices/providers/OKXPriceProvider.d.ts +7 -1
- package/dist/prices/providers/OKXPriceProvider.js +7 -1
- package/dist/prices/providers/abstract/ExchangePriceProvider.d.ts +3 -0
- package/dist/prices/providers/abstract/ExchangePriceProvider.js +3 -0
- package/dist/storage/IUnifiedStorage.d.ts +19 -7
- package/dist/storage/UnifiedSwapStorage.d.ts +33 -3
- package/dist/storage/UnifiedSwapStorage.js +29 -1
- package/dist/storage-browser/IndexedDBUnifiedStorage.d.ts +31 -7
- package/dist/storage-browser/IndexedDBUnifiedStorage.js +29 -6
- package/dist/storage-browser/LocalStorageManager.d.ts +25 -1
- package/dist/storage-browser/LocalStorageManager.js +25 -1
- package/dist/swapper/Swapper.d.ts +380 -226
- package/dist/swapper/Swapper.js +383 -349
- package/dist/swapper/SwapperFactory.d.ts +66 -18
- package/dist/swapper/SwapperFactory.js +24 -3
- package/dist/swapper/SwapperUtils.d.ts +75 -28
- package/dist/swapper/SwapperUtils.js +107 -60
- package/dist/swapper/SwapperWithChain.d.ts +286 -91
- package/dist/swapper/SwapperWithChain.js +218 -64
- package/dist/swapper/SwapperWithSigner.d.ts +229 -80
- package/dist/swapper/SwapperWithSigner.js +190 -44
- package/dist/swaps/IAddressSwap.d.ts +12 -3
- package/dist/swaps/IAddressSwap.js +3 -2
- package/dist/swaps/IBTCWalletSwap.d.ts +26 -8
- package/dist/swaps/IBTCWalletSwap.js +3 -2
- package/dist/swaps/IClaimableSwap.d.ts +38 -6
- package/dist/swaps/IClaimableSwap.js +3 -2
- package/dist/swaps/IClaimableSwapWrapper.d.ts +11 -1
- package/dist/swaps/IRefundableSwap.d.ts +31 -5
- package/dist/swaps/IRefundableSwap.js +3 -2
- package/dist/swaps/ISwap.d.ts +162 -24
- package/dist/swaps/ISwap.js +92 -35
- package/dist/swaps/ISwapWithGasDrop.d.ts +8 -2
- package/dist/swaps/ISwapWithGasDrop.js +2 -1
- package/dist/swaps/ISwapWrapper.d.ts +161 -52
- package/dist/swaps/ISwapWrapper.js +131 -73
- package/dist/swaps/escrow_swaps/IEscrowSelfInitSwap.d.ts +51 -6
- package/dist/swaps/escrow_swaps/IEscrowSelfInitSwap.js +22 -12
- package/dist/swaps/escrow_swaps/IEscrowSwap.d.ts +65 -12
- package/dist/swaps/escrow_swaps/IEscrowSwap.js +38 -19
- package/dist/swaps/escrow_swaps/IEscrowSwapWrapper.d.ts +39 -9
- package/dist/swaps/escrow_swaps/IEscrowSwapWrapper.js +30 -21
- package/dist/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.d.ts +31 -15
- package/dist/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.js +33 -18
- package/dist/swaps/escrow_swaps/frombtc/IFromBTCSelfInitSwap.d.ts +97 -28
- package/dist/swaps/escrow_swaps/frombtc/IFromBTCSelfInitSwap.js +91 -27
- package/dist/swaps/escrow_swaps/frombtc/IFromBTCWrapper.d.ts +22 -9
- package/dist/swaps/escrow_swaps/frombtc/IFromBTCWrapper.js +24 -11
- package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.d.ts +278 -60
- package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.js +519 -241
- package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.d.ts +77 -26
- package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.js +132 -50
- package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.d.ts +313 -52
- package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.js +544 -194
- package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.d.ts +87 -26
- package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.js +147 -58
- package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.d.ts +222 -55
- package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.js +462 -244
- package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.d.ts +77 -23
- package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.js +116 -46
- package/dist/swaps/escrow_swaps/tobtc/IToBTCSwap.d.ts +195 -58
- package/dist/swaps/escrow_swaps/tobtc/IToBTCSwap.js +324 -191
- package/dist/swaps/escrow_swaps/tobtc/IToBTCWrapper.d.ts +30 -5
- package/dist/swaps/escrow_swaps/tobtc/IToBTCWrapper.js +44 -19
- package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNSwap.d.ts +61 -20
- package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNSwap.js +75 -32
- package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.d.ts +76 -50
- package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.js +106 -101
- package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCSwap.d.ts +37 -14
- package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCSwap.js +66 -20
- package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.d.ts +46 -17
- package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.js +82 -27
- package/dist/swaps/spv_swaps/SpvFromBTCSwap.d.ts +350 -88
- package/dist/swaps/spv_swaps/SpvFromBTCSwap.js +482 -215
- package/dist/swaps/spv_swaps/SpvFromBTCWrapper.d.ts +76 -24
- package/dist/swaps/spv_swaps/SpvFromBTCWrapper.js +247 -124
- package/dist/swaps/trusted/ln/LnForGasSwap.d.ts +148 -20
- package/dist/swaps/trusted/ln/LnForGasSwap.js +175 -45
- package/dist/swaps/trusted/ln/LnForGasWrapper.d.ts +29 -10
- package/dist/swaps/trusted/ln/LnForGasWrapper.js +30 -11
- package/dist/swaps/trusted/onchain/OnchainForGasSwap.d.ts +202 -49
- package/dist/swaps/trusted/onchain/OnchainForGasSwap.js +232 -80
- package/dist/swaps/trusted/onchain/OnchainForGasWrapper.d.ts +34 -12
- package/dist/swaps/trusted/onchain/OnchainForGasWrapper.js +33 -14
- package/dist/types/AmountData.d.ts +2 -1
- package/dist/types/CustomPriceFunction.d.ts +8 -2
- package/dist/types/PriceInfoType.d.ts +4 -4
- package/dist/types/PriceInfoType.js +3 -3
- package/dist/types/SwapExecutionAction.d.ts +85 -4
- package/dist/types/SwapWithSigner.d.ts +5 -2
- package/dist/types/SwapWithSigner.js +5 -2
- package/dist/types/Token.d.ts +11 -5
- package/dist/types/Token.js +6 -3
- package/dist/types/TokenAmount.d.ts +3 -0
- package/dist/types/TokenAmount.js +2 -0
- package/dist/types/fees/Fee.d.ts +3 -2
- package/dist/types/fees/FeeBreakdown.d.ts +3 -2
- package/dist/types/fees/PercentagePPM.d.ts +4 -2
- package/dist/types/fees/PercentagePPM.js +2 -1
- package/dist/types/lnurl/LNURLPay.d.ts +20 -12
- package/dist/types/lnurl/LNURLPay.js +8 -4
- package/dist/types/lnurl/LNURLWithdraw.d.ts +17 -10
- package/dist/types/lnurl/LNURLWithdraw.js +8 -4
- package/dist/types/wallets/LightningInvoiceCreateService.d.ts +24 -0
- package/dist/types/wallets/LightningInvoiceCreateService.js +15 -0
- package/dist/types/wallets/MinimalBitcoinWalletInterface.d.ts +3 -1
- package/dist/types/wallets/MinimalLightningNetworkWalletInterface.d.ts +4 -2
- package/dist/utils/BitcoinUtils.d.ts +1 -0
- package/dist/utils/BitcoinUtils.js +5 -1
- package/dist/utils/SwapUtils.d.ts +58 -1
- package/dist/utils/SwapUtils.js +55 -1
- package/dist/utils/TokenUtils.d.ts +10 -2
- package/dist/utils/TokenUtils.js +12 -4
- package/package.json +3 -3
- package/src/bitcoin/coinselect2/utils.ts +6 -0
- package/src/bitcoin/wallet/BitcoinWallet.ts +41 -5
- package/src/bitcoin/wallet/IBitcoinWallet.ts +57 -2
- package/src/bitcoin/wallet/SingleAddressBitcoinWallet.ts +42 -6
- package/src/enums/FeeType.ts +8 -1
- package/src/enums/SwapAmountType.ts +7 -0
- package/src/enums/SwapDirection.ts +7 -0
- package/src/enums/SwapType.ts +62 -2
- package/src/errors/IntermediaryError.ts +4 -0
- package/src/errors/RequestError.ts +15 -1
- package/src/errors/UserError.ts +1 -0
- package/src/index.ts +12 -5
- package/src/intermediaries/Intermediary.ts +61 -14
- package/src/intermediaries/IntermediaryDiscovery.ts +69 -34
- package/src/prices/RedundantSwapPrice.ts +26 -6
- package/src/prices/SingleSwapPrice.ts +11 -8
- package/src/prices/SwapPriceWithChain.ts +63 -26
- package/src/prices/abstract/IPriceProvider.ts +4 -4
- package/src/prices/abstract/ISwapPrice.ts +115 -66
- package/src/prices/providers/BinancePriceProvider.ts +8 -1
- package/src/prices/providers/CoinGeckoPriceProvider.ts +7 -1
- package/src/prices/providers/CoinPaprikaPriceProvider.ts +7 -1
- package/src/prices/providers/CustomPriceProvider.ts +12 -1
- package/src/prices/providers/KrakenPriceProvider.ts +10 -1
- package/src/prices/providers/OKXPriceProvider.ts +7 -1
- package/src/prices/providers/abstract/ExchangePriceProvider.ts +3 -0
- package/src/storage/IUnifiedStorage.ts +19 -7
- package/src/storage/UnifiedSwapStorage.ts +33 -3
- package/src/storage-browser/IndexedDBUnifiedStorage.ts +31 -8
- package/src/storage-browser/LocalStorageManager.ts +25 -1
- package/src/swapper/Swapper.ts +599 -390
- package/src/swapper/SwapperFactory.ts +73 -24
- package/src/swapper/SwapperUtils.ts +107 -60
- package/src/swapper/SwapperWithChain.ts +320 -81
- package/src/swapper/SwapperWithSigner.ts +263 -56
- package/src/swaps/IAddressSwap.ts +13 -3
- package/src/swaps/IBTCWalletSwap.ts +26 -10
- package/src/swaps/IClaimableSwap.ts +41 -6
- package/src/swaps/IClaimableSwapWrapper.ts +11 -2
- package/src/swaps/IRefundableSwap.ts +34 -5
- package/src/swaps/ISwap.ts +224 -85
- package/src/swaps/ISwapWithGasDrop.ts +8 -2
- package/src/swaps/ISwapWrapper.ts +216 -98
- package/src/swaps/escrow_swaps/IEscrowSelfInitSwap.ts +64 -18
- package/src/swaps/escrow_swaps/IEscrowSwap.ts +83 -37
- package/src/swaps/escrow_swaps/IEscrowSwapWrapper.ts +61 -30
- package/src/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.ts +37 -19
- package/src/swaps/escrow_swaps/frombtc/IFromBTCSelfInitSwap.ts +123 -50
- package/src/swaps/escrow_swaps/frombtc/IFromBTCWrapper.ts +24 -11
- package/src/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.ts +562 -258
- package/src/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.ts +156 -62
- package/src/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.ts +592 -227
- package/src/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.ts +177 -74
- package/src/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.ts +483 -245
- package/src/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.ts +141 -59
- package/src/swaps/escrow_swaps/tobtc/IToBTCSwap.ts +350 -195
- package/src/swaps/escrow_swaps/tobtc/IToBTCWrapper.ts +48 -23
- package/src/swaps/escrow_swaps/tobtc/ln/ToBTCLNSwap.ts +87 -40
- package/src/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.ts +110 -110
- package/src/swaps/escrow_swaps/tobtc/onchain/ToBTCSwap.ts +89 -34
- package/src/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.ts +101 -31
- package/src/swaps/spv_swaps/SpvFromBTCSwap.ts +556 -259
- package/src/swaps/spv_swaps/SpvFromBTCWrapper.ts +292 -148
- package/src/swaps/trusted/ln/LnForGasSwap.ts +186 -47
- package/src/swaps/trusted/ln/LnForGasWrapper.ts +34 -15
- package/src/swaps/trusted/onchain/OnchainForGasSwap.ts +262 -88
- package/src/swaps/trusted/onchain/OnchainForGasWrapper.ts +41 -19
- package/src/types/AmountData.ts +2 -1
- package/src/types/CustomPriceFunction.ts +8 -2
- package/src/types/PriceInfoType.ts +4 -4
- package/src/types/SwapExecutionAction.ts +97 -5
- package/src/types/SwapWithSigner.ts +8 -4
- package/src/types/Token.ts +12 -5
- package/src/types/TokenAmount.ts +3 -0
- package/src/types/fees/Fee.ts +3 -2
- package/src/types/fees/FeeBreakdown.ts +3 -2
- package/src/types/fees/PercentagePPM.ts +4 -2
- package/src/types/lnurl/LNURLPay.ts +20 -12
- package/src/types/lnurl/LNURLWithdraw.ts +17 -10
- package/src/types/wallets/LightningInvoiceCreateService.ts +30 -0
- package/src/types/wallets/MinimalBitcoinWalletInterface.ts +3 -1
- package/src/types/wallets/MinimalLightningNetworkWalletInterface.ts +4 -2
- package/src/utils/BitcoinUtils.ts +5 -0
- package/src/utils/SwapUtils.ts +63 -1
- package/src/utils/TokenUtils.ts +12 -4
- package/dist/bitcoin/BitcoinRpcWithAddressIndex.d.ts +0 -68
- package/dist/bitcoin/BitcoinRpcWithAddressIndex.js +0 -2
- package/dist/bitcoin/LightningNetworkApi.d.ts +0 -12
- package/dist/bitcoin/LightningNetworkApi.js +0 -2
- package/dist/bitcoin/mempool/MempoolApi.d.ts +0 -350
- package/dist/bitcoin/mempool/MempoolApi.js +0 -311
- package/dist/bitcoin/mempool/MempoolBitcoinBlock.d.ts +0 -44
- package/dist/bitcoin/mempool/MempoolBitcoinBlock.js +0 -48
- package/dist/bitcoin/mempool/MempoolBitcoinRpc.d.ts +0 -119
- package/dist/bitcoin/mempool/MempoolBitcoinRpc.js +0 -361
- package/dist/bitcoin/mempool/synchronizer/MempoolBtcRelaySynchronizer.d.ts +0 -22
- package/dist/bitcoin/mempool/synchronizer/MempoolBtcRelaySynchronizer.js +0 -105
- package/dist/errors/PaymentAuthError.d.ts +0 -11
- package/dist/errors/PaymentAuthError.js +0 -23
- package/src/errors/PaymentAuthError.ts +0 -26
|
@@ -12,15 +12,16 @@ import {
|
|
|
12
12
|
import {Buffer} from "buffer";
|
|
13
13
|
import {
|
|
14
14
|
extendAbortController,
|
|
15
|
-
getTxoHash
|
|
15
|
+
getTxoHash, toBigInt
|
|
16
16
|
} from "../../../../utils/Utils";
|
|
17
17
|
import {
|
|
18
|
+
fromOutputScript,
|
|
18
19
|
parsePsbtTransaction,
|
|
19
20
|
toOutputScript,
|
|
20
21
|
} from "../../../../utils/BitcoinUtils";
|
|
21
22
|
import {IBitcoinWallet, isIBitcoinWallet} from "../../../../bitcoin/wallet/IBitcoinWallet";
|
|
22
23
|
import {IBTCWalletSwap} from "../../../IBTCWalletSwap";
|
|
23
|
-
import {
|
|
24
|
+
import {Transaction} from "@scure/btc-signer";
|
|
24
25
|
import {SingleAddressBitcoinWallet} from "../../../../bitcoin/wallet/SingleAddressBitcoinWallet";
|
|
25
26
|
import {
|
|
26
27
|
MinimalBitcoinWalletInterface,
|
|
@@ -30,59 +31,111 @@ import {IClaimableSwap} from "../../../IClaimableSwap";
|
|
|
30
31
|
import {IEscrowSelfInitSwapInit, isIEscrowSelfInitSwapInit} from "../../IEscrowSelfInitSwap";
|
|
31
32
|
import {IAddressSwap} from "../../../IAddressSwap";
|
|
32
33
|
import {TokenAmount, toTokenAmount} from "../../../../types/TokenAmount";
|
|
33
|
-
import {BitcoinTokens, BtcToken, SCToken
|
|
34
|
+
import {BitcoinTokens, BtcToken, SCToken} from "../../../../types/Token";
|
|
34
35
|
import {getLogger, LoggerType} from "../../../../utils/Logger";
|
|
35
36
|
import {toBitcoinWallet} from "../../../../utils/BitcoinWalletUtils";
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
|
-
* State enum for
|
|
39
|
-
*
|
|
39
|
+
* State enum for legacy escrow based Bitcoin -> Smart chain swaps.
|
|
40
|
+
*
|
|
41
|
+
* @category Swaps/Legacy/Bitcoin → Smart chain
|
|
40
42
|
*/
|
|
41
43
|
export enum FromBTCSwapState {
|
|
44
|
+
/**
|
|
45
|
+
* Bitcoin swap address has expired and the intermediary (LP) has already refunded
|
|
46
|
+
* its funds. No BTC should be sent anymore!
|
|
47
|
+
*/
|
|
42
48
|
FAILED = -4,
|
|
49
|
+
/**
|
|
50
|
+
* Bitcoin swap address has expired, user should not send any BTC anymore! Though
|
|
51
|
+
* the intermediary (LP) hasn't refunded yet. So if there is a transaction already
|
|
52
|
+
* in-flight the swap might still succeed.
|
|
53
|
+
*/
|
|
43
54
|
EXPIRED = -3,
|
|
55
|
+
/**
|
|
56
|
+
* Swap has expired for good and there is no way how it can be executed anymore
|
|
57
|
+
*/
|
|
44
58
|
QUOTE_EXPIRED = -2,
|
|
59
|
+
/**
|
|
60
|
+
* A swap is almost expired, and it should be presented to the user as expired, though
|
|
61
|
+
* there is still a chance that it will be processed
|
|
62
|
+
*/
|
|
45
63
|
QUOTE_SOFT_EXPIRED = -1,
|
|
64
|
+
/**
|
|
65
|
+
* Swap quote was created, use the {@link FromBTCSwap.commit} or {@link FromBTCSwap.txsCommit} functions
|
|
66
|
+
* to initiate it by creating the swap escrow on the destination smart chain
|
|
67
|
+
*/
|
|
46
68
|
PR_CREATED = 0,
|
|
69
|
+
/**
|
|
70
|
+
* Swap escrow was initiated (committed) on the destination chain, user can send the BTC to the
|
|
71
|
+
* swap address with the {@link FromBTCSwap.getFundedPsbt}, {@link FromBTCSwap.getAddress} or
|
|
72
|
+
* {@link FromBTCSwap.getHyperlink} functions.
|
|
73
|
+
*/
|
|
47
74
|
CLAIM_COMMITED = 1,
|
|
75
|
+
/**
|
|
76
|
+
* Input bitcoin transaction was confirmed, wait for automatic settlement by the watchtower
|
|
77
|
+
* or settle manually using the {@link FromBTCSwap.claim} or {@link FromBTCSwap.txsClaim}
|
|
78
|
+
* function.
|
|
79
|
+
*/
|
|
48
80
|
BTC_TX_CONFIRMED = 2,
|
|
81
|
+
/**
|
|
82
|
+
* Swap successfully settled and funds received on the destination chain
|
|
83
|
+
*/
|
|
49
84
|
CLAIM_CLAIMED = 3
|
|
50
85
|
}
|
|
51
86
|
|
|
52
87
|
export type FromBTCSwapInit<T extends SwapData> = IEscrowSelfInitSwapInit<T> & {
|
|
53
88
|
data: T;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
requiredConfirmations: number;
|
|
89
|
+
address?: string;
|
|
90
|
+
amount?: bigint;
|
|
91
|
+
requiredConfirmations?: number;
|
|
58
92
|
};
|
|
59
93
|
|
|
60
94
|
export function isFromBTCSwapInit<T extends SwapData>(obj: any): obj is FromBTCSwapInit<T> {
|
|
61
|
-
return typeof(obj.
|
|
62
|
-
typeof(obj.
|
|
63
|
-
typeof(obj.
|
|
64
|
-
typeof(obj.requiredConfirmations) === "number" &&
|
|
95
|
+
return typeof(obj.data) === "object" &&
|
|
96
|
+
(obj.address==null || typeof(obj.address) === "string") &&
|
|
97
|
+
(obj.amount==null || typeof(obj.amount) === "bigint") &&
|
|
98
|
+
(obj.requiredConfirmations==null || typeof(obj.requiredConfirmations) === "number") &&
|
|
65
99
|
isIEscrowSelfInitSwapInit<T>(obj);
|
|
66
100
|
}
|
|
67
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Legacy escrow (PrTLC) based swap for Bitcoin -> Smart chains, requires manual initiation
|
|
104
|
+
* of the swap escrow on the destination chain.
|
|
105
|
+
*
|
|
106
|
+
* @category Swaps/Legacy/Bitcoin → Smart chain
|
|
107
|
+
*/
|
|
68
108
|
export class FromBTCSwap<T extends ChainType = ChainType>
|
|
69
109
|
extends IFromBTCSelfInitSwap<T, FromBTCDefinition<T>, FromBTCSwapState>
|
|
70
110
|
implements IBTCWalletSwap, IClaimableSwap<T, FromBTCDefinition<T>, FromBTCSwapState>, IAddressSwap {
|
|
71
111
|
|
|
112
|
+
protected readonly TYPE: SwapType.FROM_BTC = SwapType.FROM_BTC;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
72
117
|
protected readonly logger: LoggerType;
|
|
118
|
+
/**
|
|
119
|
+
* @internal
|
|
120
|
+
*/
|
|
73
121
|
protected readonly inputToken: BtcToken<false> = BitcoinTokens.BTC;
|
|
74
|
-
|
|
122
|
+
/**
|
|
123
|
+
* @internal
|
|
124
|
+
*/
|
|
125
|
+
protected readonly feeRate!: string;
|
|
75
126
|
|
|
76
|
-
|
|
77
|
-
|
|
127
|
+
/**
|
|
128
|
+
* @internal
|
|
129
|
+
*/
|
|
130
|
+
readonly _data!: T["Data"];
|
|
78
131
|
|
|
79
|
-
address
|
|
80
|
-
amount
|
|
81
|
-
|
|
132
|
+
private address?: string;
|
|
133
|
+
private amount?: bigint;
|
|
134
|
+
private requiredConfirmations?: number;
|
|
82
135
|
|
|
83
|
-
senderAddress?: string;
|
|
84
|
-
txId?: string;
|
|
85
|
-
vout?: number;
|
|
136
|
+
private senderAddress?: string;
|
|
137
|
+
private txId?: string;
|
|
138
|
+
private vout?: number;
|
|
86
139
|
|
|
87
140
|
constructor(wrapper: FromBTCWrapper<T>, init: FromBTCSwapInit<T["Data"]>);
|
|
88
141
|
constructor(wrapper: FromBTCWrapper<T>, obj: any);
|
|
@@ -90,48 +143,56 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
90
143
|
if(isFromBTCSwapInit(initOrObject) && initOrObject.url!=null) initOrObject.url += "/frombtc";
|
|
91
144
|
super(wrapper, initOrObject);
|
|
92
145
|
if(isFromBTCSwapInit(initOrObject)) {
|
|
93
|
-
this.
|
|
94
|
-
this.
|
|
146
|
+
this._state = FromBTCSwapState.PR_CREATED;
|
|
147
|
+
this._data = initOrObject.data;
|
|
95
148
|
this.feeRate = initOrObject.feeRate;
|
|
96
149
|
this.address = initOrObject.address;
|
|
97
150
|
this.amount = initOrObject.amount;
|
|
98
151
|
this.requiredConfirmations = initOrObject.requiredConfirmations;
|
|
99
152
|
} else {
|
|
100
153
|
this.address = initOrObject.address;
|
|
101
|
-
this.amount =
|
|
154
|
+
this.amount = toBigInt(initOrObject.amount);
|
|
102
155
|
this.senderAddress = initOrObject.senderAddress;
|
|
103
156
|
this.txId = initOrObject.txId;
|
|
104
157
|
this.vout = initOrObject.vout;
|
|
105
|
-
this.requiredConfirmations = initOrObject.requiredConfirmations ?? this.
|
|
158
|
+
this.requiredConfirmations = initOrObject.requiredConfirmations ?? this._data.getConfirmationsHint();
|
|
106
159
|
}
|
|
107
160
|
this.tryRecomputeSwapPrice();
|
|
108
161
|
this.logger = getLogger("FromBTC("+this.getIdentifierHashString()+"): ");
|
|
109
162
|
}
|
|
110
163
|
|
|
164
|
+
/**
|
|
165
|
+
* @inheritDoc
|
|
166
|
+
* @internal
|
|
167
|
+
*/
|
|
111
168
|
protected getSwapData(): T["Data"] {
|
|
112
|
-
return this.
|
|
169
|
+
return this._data;
|
|
113
170
|
}
|
|
114
171
|
|
|
172
|
+
/**
|
|
173
|
+
* @inheritDoc
|
|
174
|
+
* @internal
|
|
175
|
+
*/
|
|
115
176
|
protected upgradeVersion() {
|
|
116
177
|
if(this.version == null) {
|
|
117
|
-
switch(this.
|
|
178
|
+
switch(this._state) {
|
|
118
179
|
case -2:
|
|
119
|
-
this.
|
|
180
|
+
this._state = FromBTCSwapState.FAILED
|
|
120
181
|
break;
|
|
121
182
|
case -1:
|
|
122
|
-
this.
|
|
183
|
+
this._state = FromBTCSwapState.QUOTE_EXPIRED
|
|
123
184
|
break;
|
|
124
185
|
case 0:
|
|
125
|
-
this.
|
|
186
|
+
this._state = FromBTCSwapState.PR_CREATED
|
|
126
187
|
break;
|
|
127
188
|
case 1:
|
|
128
|
-
this.
|
|
189
|
+
this._state = FromBTCSwapState.CLAIM_COMMITED
|
|
129
190
|
break;
|
|
130
191
|
case 2:
|
|
131
|
-
this.
|
|
192
|
+
this._state = FromBTCSwapState.BTC_TX_CONFIRMED
|
|
132
193
|
break;
|
|
133
194
|
case 3:
|
|
134
|
-
this.
|
|
195
|
+
this._state = FromBTCSwapState.CLAIM_CLAIMED
|
|
135
196
|
break;
|
|
136
197
|
}
|
|
137
198
|
this.version = 1;
|
|
@@ -146,8 +207,8 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
146
207
|
* Returns bitcoin address where the on-chain BTC should be sent to
|
|
147
208
|
*/
|
|
148
209
|
getAddress(): string {
|
|
149
|
-
if(this.
|
|
150
|
-
return this.address;
|
|
210
|
+
if(this._state===FromBTCSwapState.PR_CREATED) throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
|
|
211
|
+
return this.address ?? "";
|
|
151
212
|
}
|
|
152
213
|
|
|
153
214
|
/**
|
|
@@ -156,18 +217,27 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
156
217
|
* @private
|
|
157
218
|
*/
|
|
158
219
|
private _getHyperlink(): string {
|
|
159
|
-
return "bitcoin:"+this.address+"?amount="+encodeURIComponent((Number(this.amount) / 100000000).toString(10));
|
|
220
|
+
return this.address==null || this.amount==null ? "" : "bitcoin:"+this.address+"?amount="+encodeURIComponent((Number(this.amount) / 100000000).toString(10));
|
|
160
221
|
}
|
|
161
222
|
|
|
223
|
+
/**
|
|
224
|
+
* @inheritDoc
|
|
225
|
+
*/
|
|
162
226
|
getHyperlink(): string {
|
|
163
|
-
if(this.
|
|
227
|
+
if(this._state===FromBTCSwapState.PR_CREATED) throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
|
|
164
228
|
return this._getHyperlink();
|
|
165
229
|
}
|
|
166
230
|
|
|
231
|
+
/**
|
|
232
|
+
* @inheritDoc
|
|
233
|
+
*/
|
|
167
234
|
getInputAddress(): string | null {
|
|
168
235
|
return this.senderAddress ?? null;
|
|
169
236
|
}
|
|
170
237
|
|
|
238
|
+
/**
|
|
239
|
+
* @inheritDoc
|
|
240
|
+
*/
|
|
171
241
|
getInputTxId(): string | null {
|
|
172
242
|
return this.txId ?? null;
|
|
173
243
|
}
|
|
@@ -177,74 +247,136 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
177
247
|
* to that address anymore
|
|
178
248
|
*/
|
|
179
249
|
getTimeoutTime(): number {
|
|
180
|
-
return Number(this.wrapper.
|
|
250
|
+
return Number(this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations ?? 6)) * 1000;
|
|
181
251
|
}
|
|
182
252
|
|
|
253
|
+
/**
|
|
254
|
+
* @inheritDoc
|
|
255
|
+
*/
|
|
183
256
|
requiresAction(): boolean {
|
|
184
|
-
return this.isClaimable() || (this.
|
|
257
|
+
return this.isClaimable() || (this._state===FromBTCSwapState.CLAIM_COMMITED && this.getTimeoutTime()>Date.now() && this.txId==null);
|
|
185
258
|
}
|
|
186
259
|
|
|
260
|
+
/**
|
|
261
|
+
* @inheritDoc
|
|
262
|
+
*/
|
|
187
263
|
isFinished(): boolean {
|
|
188
|
-
return this.
|
|
264
|
+
return this._state===FromBTCSwapState.CLAIM_CLAIMED || this._state===FromBTCSwapState.QUOTE_EXPIRED || this._state===FromBTCSwapState.FAILED;
|
|
189
265
|
}
|
|
190
266
|
|
|
267
|
+
/**
|
|
268
|
+
* @inheritDoc
|
|
269
|
+
*/
|
|
191
270
|
isClaimable(): boolean {
|
|
192
|
-
return this.
|
|
271
|
+
return this._state===FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
193
272
|
}
|
|
194
273
|
|
|
274
|
+
/**
|
|
275
|
+
* @inheritDoc
|
|
276
|
+
*/
|
|
195
277
|
isSuccessful(): boolean {
|
|
196
|
-
return this.
|
|
278
|
+
return this._state===FromBTCSwapState.CLAIM_CLAIMED;
|
|
197
279
|
}
|
|
198
280
|
|
|
281
|
+
/**
|
|
282
|
+
* @inheritDoc
|
|
283
|
+
*/
|
|
199
284
|
isFailed(): boolean {
|
|
200
|
-
return this.
|
|
285
|
+
return this._state===FromBTCSwapState.FAILED || this._state===FromBTCSwapState.EXPIRED;
|
|
201
286
|
}
|
|
202
287
|
|
|
288
|
+
/**
|
|
289
|
+
* @inheritDoc
|
|
290
|
+
*/
|
|
203
291
|
isQuoteExpired(): boolean {
|
|
204
|
-
return this.
|
|
292
|
+
return this._state===FromBTCSwapState.QUOTE_EXPIRED;
|
|
205
293
|
}
|
|
206
294
|
|
|
295
|
+
/**
|
|
296
|
+
* @inheritDoc
|
|
297
|
+
*/
|
|
207
298
|
isQuoteSoftExpired(): boolean {
|
|
208
|
-
return this.
|
|
299
|
+
return this._state===FromBTCSwapState.QUOTE_EXPIRED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED;
|
|
209
300
|
}
|
|
210
301
|
|
|
302
|
+
/**
|
|
303
|
+
* @inheritDoc
|
|
304
|
+
* @internal
|
|
305
|
+
*/
|
|
211
306
|
protected canCommit(): boolean {
|
|
212
|
-
if(this.
|
|
213
|
-
|
|
307
|
+
if(this._state!==FromBTCSwapState.PR_CREATED) return false;
|
|
308
|
+
if(this.requiredConfirmations==null) return false;
|
|
309
|
+
const expiry = this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations);
|
|
214
310
|
const currentTimestamp = BigInt(Math.floor(Date.now()/1000));
|
|
215
311
|
|
|
216
|
-
return (expiry - currentTimestamp) >= this.wrapper.
|
|
312
|
+
return (expiry - currentTimestamp) >= this.wrapper._options.minSendWindow;
|
|
217
313
|
}
|
|
218
314
|
|
|
219
315
|
|
|
220
316
|
//////////////////////////////
|
|
221
317
|
//// Amounts & fees
|
|
222
318
|
|
|
319
|
+
/**
|
|
320
|
+
* @inheritDoc
|
|
321
|
+
*/
|
|
223
322
|
getInputToken(): BtcToken<false> {
|
|
224
323
|
return BitcoinTokens.BTC;
|
|
225
324
|
}
|
|
226
325
|
|
|
326
|
+
/**
|
|
327
|
+
* @inheritDoc
|
|
328
|
+
*/
|
|
227
329
|
getInput(): TokenAmount<T["ChainId"], BtcToken<false>> {
|
|
228
|
-
return toTokenAmount(this.amount, this.inputToken, this.wrapper.
|
|
330
|
+
return toTokenAmount(this.amount ?? null, this.inputToken, this.wrapper._prices);
|
|
229
331
|
}
|
|
230
332
|
|
|
231
333
|
/**
|
|
232
|
-
* Returns claimer bounty, acting as a reward for watchtowers to claim the swap automatically
|
|
334
|
+
* Returns claimer bounty, acting as a reward for watchtowers to claim the swap automatically,
|
|
335
|
+
* this amount is pre-funded by the user on the destination chain when the swap escrow
|
|
336
|
+
* is initiated. For total pre-funded deposit amount see {@link getTotalDeposit}.
|
|
233
337
|
*/
|
|
234
338
|
getClaimerBounty(): TokenAmount<T["ChainId"], SCToken<T["ChainId"]>, true> {
|
|
235
|
-
return toTokenAmount(this.
|
|
339
|
+
return toTokenAmount(this._data.getClaimerBounty(), this.wrapper._tokens[this._data.getDepositToken()], this.wrapper._prices);
|
|
236
340
|
}
|
|
237
341
|
|
|
238
342
|
|
|
239
343
|
//////////////////////////////
|
|
240
344
|
//// Bitcoin tx
|
|
241
345
|
|
|
346
|
+
/**
|
|
347
|
+
* If the required number of confirmations is not known, this function tries to infer it by looping through
|
|
348
|
+
* possible confirmation targets and comparing the claim hashes
|
|
349
|
+
*
|
|
350
|
+
* @param btcTx Bitcoin transaction
|
|
351
|
+
* @param vout Output index of the desired output in the bitcoin transaction
|
|
352
|
+
*
|
|
353
|
+
* @private
|
|
354
|
+
*/
|
|
355
|
+
private inferRequiredConfirmationsCount(btcTx: Omit<BtcTxWithBlockheight, "hex" | "raw">, vout: number): number | undefined {
|
|
356
|
+
const txOut = btcTx.outs[vout];
|
|
357
|
+
for(let i=1;i<=20;i++) {
|
|
358
|
+
const computedClaimHash = this.wrapper._contract.getHashForOnchain(
|
|
359
|
+
Buffer.from(txOut.scriptPubKey.hex, "hex"),
|
|
360
|
+
BigInt(txOut.value),
|
|
361
|
+
i
|
|
362
|
+
);
|
|
363
|
+
if(computedClaimHash.toString("hex")===this._data.getClaimHash()) {
|
|
364
|
+
return i;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* @inheritDoc
|
|
371
|
+
*/
|
|
242
372
|
getRequiredConfirmationsCount(): number {
|
|
243
|
-
return this.requiredConfirmations;
|
|
373
|
+
return this.requiredConfirmations ?? NaN;
|
|
244
374
|
}
|
|
245
375
|
|
|
246
376
|
/**
|
|
247
|
-
* Checks whether a bitcoin payment was already made, returns the payment or null when no payment has been made.
|
|
377
|
+
* Checks whether a bitcoin payment was already made, returns the payment or `null` when no payment has been made.
|
|
378
|
+
*
|
|
379
|
+
* @internal
|
|
248
380
|
*/
|
|
249
381
|
protected async getBitcoinPayment(): Promise<{
|
|
250
382
|
txId: string,
|
|
@@ -253,32 +385,39 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
253
385
|
targetConfirmations: number,
|
|
254
386
|
inputAddresses?: string[]
|
|
255
387
|
} | null> {
|
|
256
|
-
const txoHashHint = this.
|
|
257
|
-
if(txoHashHint==null) throw new Error("Swap data
|
|
388
|
+
const txoHashHint = this._data.getTxoHashHint();
|
|
389
|
+
if(txoHashHint==null) throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
|
|
390
|
+
if(this.address==null) throw new Error("Cannot check bitcoin payment, because the address is not known! This can happen after a swap is recovered.");
|
|
258
391
|
|
|
259
|
-
const result = await this.wrapper.
|
|
392
|
+
const result = await this.wrapper._btcRpc.checkAddressTxos(this.address, Buffer.from(txoHashHint, "hex"));
|
|
260
393
|
if(result==null) return null;
|
|
261
394
|
|
|
395
|
+
if(this.requiredConfirmations==null) {
|
|
396
|
+
this.requiredConfirmations = this.inferRequiredConfirmationsCount(result.tx, result.vout);
|
|
397
|
+
}
|
|
398
|
+
|
|
262
399
|
return {
|
|
263
400
|
inputAddresses: result.tx.inputAddresses,
|
|
264
401
|
txId: result.tx.txid,
|
|
265
402
|
vout: result.vout,
|
|
266
403
|
confirmations: result.tx.confirmations ?? 0,
|
|
267
|
-
targetConfirmations: this.
|
|
404
|
+
targetConfirmations: this.getRequiredConfirmationsCount()
|
|
268
405
|
}
|
|
269
406
|
}
|
|
270
407
|
|
|
271
408
|
/**
|
|
272
|
-
*
|
|
409
|
+
* Used to set the txId of the bitcoin payment from the on-chain events listener
|
|
410
|
+
*
|
|
411
|
+
* @param txId Transaction ID that settled the swap on the smart chain
|
|
273
412
|
*
|
|
274
|
-
* @
|
|
413
|
+
* @internal
|
|
275
414
|
*/
|
|
276
415
|
async _setBitcoinTxId(txId: string) {
|
|
277
416
|
if(this.txId!==txId || this.address==null || this.vout==null || this.senderAddress==null || this.amount==null) {
|
|
278
|
-
const btcTx = await this.wrapper.
|
|
417
|
+
const btcTx = await this.wrapper._btcRpc.getTransaction(txId);
|
|
279
418
|
if(btcTx==null) return;
|
|
280
419
|
|
|
281
|
-
const txoHashHint = this.
|
|
420
|
+
const txoHashHint = this._data.getTxoHashHint();
|
|
282
421
|
if(txoHashHint!=null) {
|
|
283
422
|
const expectedTxoHash = Buffer.from(txoHashHint, "hex");
|
|
284
423
|
const vout = btcTx.outs.findIndex(out => getTxoHash(out.scriptPubKey.hex, out.value).equals(expectedTxoHash));
|
|
@@ -289,11 +428,13 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
289
428
|
// hence doesn't contain the address and amount data
|
|
290
429
|
if(this.amount==null) this.amount = BigInt(btcTx.outs[vout].value);
|
|
291
430
|
if(this.address==null) try {
|
|
292
|
-
|
|
293
|
-
this.address = Address(this.wrapper.options.bitcoinNetwork).encode(addressData);
|
|
431
|
+
this.address = fromOutputScript(this.wrapper._options.bitcoinNetwork, btcTx.outs[vout].scriptPubKey.hex);
|
|
294
432
|
} catch (e: any) {
|
|
295
433
|
this.logger.warn("_setBitcoinTxId(): Failed to parse address from output script: ", e);
|
|
296
434
|
}
|
|
435
|
+
if(this.requiredConfirmations==null) {
|
|
436
|
+
this.requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
|
|
437
|
+
}
|
|
297
438
|
}
|
|
298
439
|
}
|
|
299
440
|
|
|
@@ -306,40 +447,67 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
306
447
|
}
|
|
307
448
|
|
|
308
449
|
/**
|
|
309
|
-
*
|
|
450
|
+
* @inheritDoc
|
|
310
451
|
*
|
|
311
|
-
* @
|
|
312
|
-
* @param checkIntervalSeconds How often to check the bitcoin transaction
|
|
313
|
-
* @param abortSignal Abort signal
|
|
314
|
-
* @throws {Error} if in invalid state (must be CLAIM_COMMITED)
|
|
452
|
+
* @throws {Error} if in invalid state (must be {@link FromBTCSwapState.CLAIM_COMMITED})
|
|
315
453
|
*/
|
|
316
454
|
async waitForBitcoinTransaction(
|
|
317
455
|
updateCallback?: (txId?: string, confirmations?: number, targetConfirmations?: number, txEtaMs?: number) => void,
|
|
318
456
|
checkIntervalSeconds?: number,
|
|
319
457
|
abortSignal?: AbortSignal
|
|
320
458
|
): Promise<string> {
|
|
321
|
-
if(this.
|
|
322
|
-
const txoHashHint = this.
|
|
323
|
-
if(txoHashHint==null) throw new Error("Swap data
|
|
459
|
+
if(this._state!==FromBTCSwapState.CLAIM_COMMITED && this._state!==FromBTCSwapState.EXPIRED) throw new Error("Must be in COMMITED state!");
|
|
460
|
+
const txoHashHint = this._data.getTxoHashHint();
|
|
461
|
+
if(txoHashHint==null) throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
|
|
462
|
+
if(this.address==null) throw new Error("Cannot check bitcoin payment, because the address is not known! This can happen after a swap is recovered.");
|
|
463
|
+
|
|
464
|
+
let abortedDueToEnoughConfirmationsResult: {
|
|
465
|
+
tx: Omit<BtcTxWithBlockheight, "hex" | "raw">, vout: number
|
|
466
|
+
} | undefined;
|
|
467
|
+
const abortController = extendAbortController(abortSignal);
|
|
324
468
|
|
|
325
|
-
const result = await this.wrapper.
|
|
469
|
+
const result = await this.wrapper._btcRpc.waitForAddressTxo(
|
|
326
470
|
this.address,
|
|
327
471
|
Buffer.from(txoHashHint, "hex"),
|
|
328
|
-
this.requiredConfirmations,
|
|
472
|
+
this.requiredConfirmations ?? 6, //In case confirmation count is not known, we use a conservative estimate
|
|
329
473
|
(btcTx?: Omit<BtcTxWithBlockheight, "hex" | "raw">, vout?: number, txEtaMs?: number) => {
|
|
330
|
-
|
|
331
|
-
|
|
474
|
+
let requiredConfirmations = this.requiredConfirmations;
|
|
475
|
+
|
|
476
|
+
if(btcTx!=null && vout!=null && requiredConfirmations==null) {
|
|
477
|
+
requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if(btcTx!=null && (btcTx.txid!==this.txId || (this.requiredConfirmations==null && requiredConfirmations!=null))) {
|
|
332
481
|
this.txId = btcTx.txid;
|
|
333
482
|
this.vout = vout;
|
|
483
|
+
this.requiredConfirmations = requiredConfirmations;
|
|
334
484
|
if(btcTx.inputAddresses!=null) this.senderAddress = btcTx.inputAddresses[0];
|
|
335
485
|
this._saveAndEmit().catch(e => {
|
|
336
486
|
this.logger.error("waitForBitcoinTransaction(): Failed to save swap from within waitForAddressTxo callback:", e)
|
|
337
487
|
});
|
|
338
488
|
}
|
|
489
|
+
|
|
490
|
+
//Abort the loop as soon as the transaction gets enough confirmations, this is required in case
|
|
491
|
+
// we pass a default 6 confirmations to the fn, but then are able to infer the actual confirmation
|
|
492
|
+
// target from the prior block
|
|
493
|
+
if(btcTx?.confirmations!=null && requiredConfirmations!=null && requiredConfirmations<=btcTx.confirmations && vout!=null) {
|
|
494
|
+
abortedDueToEnoughConfirmationsResult = {
|
|
495
|
+
tx: btcTx,
|
|
496
|
+
vout
|
|
497
|
+
};
|
|
498
|
+
abortController.abort();
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if(updateCallback!=null) updateCallback(btcTx?.txid, btcTx==null ? undefined : (btcTx?.confirmations ?? 0), requiredConfirmations ?? NaN, txEtaMs);
|
|
339
503
|
},
|
|
340
|
-
|
|
504
|
+
abortController.signal,
|
|
341
505
|
checkIntervalSeconds
|
|
342
|
-
)
|
|
506
|
+
).catch(e => {
|
|
507
|
+
//We catch the case when the loop was aborted due to the transaction getting enough confirmations
|
|
508
|
+
if(abortedDueToEnoughConfirmationsResult!=null) return abortedDueToEnoughConfirmationsResult;
|
|
509
|
+
throw e;
|
|
510
|
+
});
|
|
343
511
|
|
|
344
512
|
if(abortSignal!=null) abortSignal.throwIfAborted();
|
|
345
513
|
|
|
@@ -348,10 +516,10 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
348
516
|
if(result.tx.inputAddresses!=null) this.senderAddress = result.tx.inputAddresses[0];
|
|
349
517
|
|
|
350
518
|
if(
|
|
351
|
-
(this.
|
|
352
|
-
(this.
|
|
519
|
+
(this._state as FromBTCSwapState)!==FromBTCSwapState.CLAIM_CLAIMED &&
|
|
520
|
+
(this._state as FromBTCSwapState)!==FromBTCSwapState.FAILED
|
|
353
521
|
) {
|
|
354
|
-
this.
|
|
522
|
+
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
355
523
|
}
|
|
356
524
|
|
|
357
525
|
await this._saveAndEmit();
|
|
@@ -360,34 +528,26 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
360
528
|
}
|
|
361
529
|
|
|
362
530
|
/**
|
|
363
|
-
*
|
|
364
|
-
* also returns inputs indices that need to be signed by the wallet before submitting the PSBT back to the SDK with
|
|
365
|
-
* `swap.submitPsbt()`
|
|
531
|
+
* Private getter of the funded PSBT that doesn't check current state
|
|
366
532
|
*
|
|
367
|
-
* @param _bitcoinWallet
|
|
368
|
-
* @param feeRate Optional fee rate
|
|
369
|
-
* @param additionalOutputs additional outputs
|
|
533
|
+
* @param _bitcoinWallet Bitcoin wallet to fund the PSBT with
|
|
534
|
+
* @param feeRate Optional bitcoin fee rate in sats/vB
|
|
535
|
+
* @param additionalOutputs Optional additional outputs that should also be included in the generated PSBT
|
|
536
|
+
*
|
|
537
|
+
* @private
|
|
370
538
|
*/
|
|
371
|
-
getFundedPsbt(
|
|
372
|
-
_bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface,
|
|
373
|
-
feeRate?: number,
|
|
374
|
-
additionalOutputs?: ({amount: bigint, outputScript: Uint8Array} | {amount: bigint, address: string})[]
|
|
375
|
-
) {
|
|
376
|
-
if(this.state!==FromBTCSwapState.CLAIM_COMMITED)
|
|
377
|
-
throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
|
|
378
|
-
return this._getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
539
|
private async _getFundedPsbt(
|
|
382
540
|
_bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface,
|
|
383
541
|
feeRate?: number,
|
|
384
542
|
additionalOutputs?: ({amount: bigint, outputScript: Uint8Array} | {amount: bigint, address: string})[]
|
|
385
543
|
): Promise<{psbt: Transaction, psbtHex: string, psbtBase64: string, signInputs: number[]}> {
|
|
544
|
+
if(this.address==null) throw new Error("Cannot create funded PSBT, because the address is not known! This can happen after a swap is recovered.");
|
|
545
|
+
|
|
386
546
|
let bitcoinWallet: IBitcoinWallet;
|
|
387
547
|
if(isIBitcoinWallet(_bitcoinWallet)) {
|
|
388
548
|
bitcoinWallet = _bitcoinWallet;
|
|
389
549
|
} else {
|
|
390
|
-
bitcoinWallet = new SingleAddressBitcoinWallet(this.wrapper.
|
|
550
|
+
bitcoinWallet = new SingleAddressBitcoinWallet(this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork, _bitcoinWallet);
|
|
391
551
|
}
|
|
392
552
|
//TODO: Maybe re-introduce fee rate check here if passed from the user
|
|
393
553
|
if(feeRate==null) {
|
|
@@ -400,12 +560,12 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
400
560
|
});
|
|
401
561
|
basePsbt.addOutput({
|
|
402
562
|
amount: this.amount,
|
|
403
|
-
script: toOutputScript(this.wrapper.
|
|
563
|
+
script: toOutputScript(this.wrapper._options.bitcoinNetwork, this.address)
|
|
404
564
|
});
|
|
405
565
|
if(additionalOutputs!=null) additionalOutputs.forEach(output => {
|
|
406
566
|
basePsbt.addOutput({
|
|
407
567
|
amount: output.amount,
|
|
408
|
-
script: (output as {outputScript: Uint8Array}).outputScript ?? toOutputScript(this.wrapper.
|
|
568
|
+
script: (output as {outputScript: Uint8Array}).outputScript ?? toOutputScript(this.wrapper._options.bitcoinNetwork, (output as {address: string}).address)
|
|
409
569
|
});
|
|
410
570
|
});
|
|
411
571
|
|
|
@@ -425,13 +585,27 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
425
585
|
}
|
|
426
586
|
|
|
427
587
|
/**
|
|
428
|
-
*
|
|
588
|
+
* @inheritDoc
|
|
589
|
+
*/
|
|
590
|
+
getFundedPsbt(
|
|
591
|
+
_bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface,
|
|
592
|
+
feeRate?: number,
|
|
593
|
+
additionalOutputs?: ({amount: bigint, outputScript: Uint8Array} | {amount: bigint, address: string})[]
|
|
594
|
+
) {
|
|
595
|
+
if(this._state!==FromBTCSwapState.CLAIM_COMMITED)
|
|
596
|
+
throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
|
|
597
|
+
return this._getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* @inheritDoc
|
|
429
602
|
*
|
|
430
|
-
* @
|
|
603
|
+
* @throws {Error} if the swap is in invalid state (not in {@link FromBTCSwapState.CLAIM_COMMITED}), or if
|
|
604
|
+
* the swap bitcoin address already expired.
|
|
431
605
|
*/
|
|
432
606
|
async submitPsbt(_psbt: Transaction | string): Promise<string> {
|
|
433
607
|
const psbt = parsePsbtTransaction(_psbt);
|
|
434
|
-
if(this.
|
|
608
|
+
if(this._state!==FromBTCSwapState.CLAIM_COMMITED)
|
|
435
609
|
throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
|
|
436
610
|
|
|
437
611
|
//Ensure not expired
|
|
@@ -440,26 +614,37 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
440
614
|
}
|
|
441
615
|
|
|
442
616
|
const output0 = psbt.getOutput(0);
|
|
443
|
-
if(output0.amount!==this.amount)
|
|
617
|
+
if(this.amount!=null && output0.amount!==this.amount)
|
|
444
618
|
throw new Error("PSBT output amount invalid, expected: "+this.amount+" got: "+output0.amount);
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
619
|
+
if(this.address!=null) {
|
|
620
|
+
const expectedOutputScript = toOutputScript(this.wrapper._options.bitcoinNetwork, this.address);
|
|
621
|
+
if(output0.script==null || !expectedOutputScript.equals(output0.script))
|
|
622
|
+
throw new Error("PSBT output script invalid!");
|
|
623
|
+
}
|
|
448
624
|
|
|
449
625
|
if(!psbt.isFinal) psbt.finalize();
|
|
450
626
|
|
|
451
|
-
return await this.wrapper.
|
|
627
|
+
return await this.wrapper._btcRpc.sendRawTransaction(Buffer.from(psbt.toBytes(true, true)).toString("hex"));
|
|
452
628
|
}
|
|
453
629
|
|
|
630
|
+
/**
|
|
631
|
+
* @inheritDoc
|
|
632
|
+
*/
|
|
454
633
|
async estimateBitcoinFee(_bitcoinWallet: IBitcoinWallet | MinimalBitcoinWalletInterface, feeRate?: number): Promise<TokenAmount<any, BtcToken<false>, true> | null> {
|
|
455
|
-
|
|
634
|
+
if(this.address==null || this.amount==null) return null;
|
|
635
|
+
const bitcoinWallet: IBitcoinWallet = toBitcoinWallet(_bitcoinWallet, this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork);
|
|
456
636
|
const txFee = await bitcoinWallet.getTransactionFee(this.address, this.amount, feeRate);
|
|
457
637
|
if(txFee==null) return null;
|
|
458
|
-
return toTokenAmount(BigInt(txFee), BitcoinTokens.BTC, this.wrapper.
|
|
638
|
+
return toTokenAmount(BigInt(txFee), BitcoinTokens.BTC, this.wrapper._prices);
|
|
459
639
|
}
|
|
460
640
|
|
|
641
|
+
/**
|
|
642
|
+
* @inheritDoc
|
|
643
|
+
*/
|
|
461
644
|
async sendBitcoinTransaction(wallet: IBitcoinWallet | MinimalBitcoinWalletInterfaceWithSigner, feeRate?: number): Promise<string> {
|
|
462
|
-
if(this.
|
|
645
|
+
if(this.address==null || this.amount==null) throw new Error("Cannot send bitcoin transaction, because the address is not known! This can happen after a swap is recovered.");
|
|
646
|
+
|
|
647
|
+
if(this._state!==FromBTCSwapState.CLAIM_COMMITED)
|
|
463
648
|
throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
|
|
464
649
|
|
|
465
650
|
//Ensure not expired
|
|
@@ -514,15 +699,15 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
514
699
|
maxWaitTillAutomaticSettlementSeconds?: number
|
|
515
700
|
}
|
|
516
701
|
): Promise<boolean> {
|
|
517
|
-
if(this.
|
|
518
|
-
if(this.
|
|
519
|
-
if(this.
|
|
520
|
-
if(this.
|
|
702
|
+
if(this._state===FromBTCSwapState.FAILED) throw new Error("Swap failed!");
|
|
703
|
+
if(this._state===FromBTCSwapState.EXPIRED) throw new Error("Swap address expired!");
|
|
704
|
+
if(this._state===FromBTCSwapState.QUOTE_EXPIRED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) throw new Error("Swap quote expired!");
|
|
705
|
+
if(this._state===FromBTCSwapState.CLAIM_CLAIMED) throw new Error("Swap already settled!");
|
|
521
706
|
|
|
522
|
-
if(this.
|
|
707
|
+
if(this._state===FromBTCSwapState.PR_CREATED) {
|
|
523
708
|
await this.commit(dstSigner, options?.abortSignal, undefined, callbacks?.onDestinationCommitSent);
|
|
524
709
|
}
|
|
525
|
-
if(this.
|
|
710
|
+
if(this._state===FromBTCSwapState.CLAIM_COMMITED) {
|
|
526
711
|
if(wallet!=null) {
|
|
527
712
|
const bitcoinPaymentSent = await this.getBitcoinPayment();
|
|
528
713
|
|
|
@@ -538,9 +723,9 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
538
723
|
}
|
|
539
724
|
|
|
540
725
|
// @ts-ignore
|
|
541
|
-
if(this.
|
|
726
|
+
if(this._state===FromBTCSwapState.CLAIM_CLAIMED) return true;
|
|
542
727
|
|
|
543
|
-
if(this.
|
|
728
|
+
if(this._state===FromBTCSwapState.BTC_TX_CONFIRMED) {
|
|
544
729
|
const success = await this.waitTillClaimed(options?.maxWaitTillAutomaticSettlementSeconds ?? 60, options?.abortSignal);
|
|
545
730
|
if(success && callbacks?.onSwapSettled!=null) callbacks.onSwapSettled(this.getOutputTxId()!);
|
|
546
731
|
return success;
|
|
@@ -549,12 +734,23 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
549
734
|
throw new Error("Invalid state reached!");
|
|
550
735
|
}
|
|
551
736
|
|
|
737
|
+
/**
|
|
738
|
+
* @inheritDoc
|
|
739
|
+
*
|
|
740
|
+
* @param options.bitcoinWallet Bitcoin wallet to use, when provided the function returns a funded
|
|
741
|
+
* psbt (`"FUNDED_PSBT"`), if not passed just a bitcoin receive address is returned (`"ADDRESS"`)
|
|
742
|
+
* @param options.skipChecks Skip checks like making sure init signature is still valid and swap
|
|
743
|
+
* wasn't commited yet (this is handled on swap creation, if you commit right after quoting, you
|
|
744
|
+
* can use `skipChecks=true`)
|
|
745
|
+
*
|
|
746
|
+
* @throws {Error} if the swap or quote is expired, or if triggered in invalid state
|
|
747
|
+
*/
|
|
552
748
|
async txsExecute(options?: {
|
|
553
749
|
bitcoinWallet?: MinimalBitcoinWalletInterface,
|
|
554
750
|
skipChecks?: boolean
|
|
555
751
|
}) {
|
|
556
|
-
if(this.
|
|
557
|
-
if(!await this.
|
|
752
|
+
if(this._state===FromBTCSwapState.PR_CREATED) {
|
|
753
|
+
if(!await this._verifyQuoteValid()) throw new Error("Quote already expired or close to expiry!");
|
|
558
754
|
if(this.getTimeoutTime()<Date.now()) throw new Error("Swap address already expired or close to expiry!");
|
|
559
755
|
return [
|
|
560
756
|
{
|
|
@@ -566,38 +762,38 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
566
762
|
{
|
|
567
763
|
name: "Payment" as const,
|
|
568
764
|
description: "Send funds to the bitcoin swap address",
|
|
569
|
-
chain: "BITCOIN",
|
|
765
|
+
chain: "BITCOIN" as const,
|
|
570
766
|
txs: [
|
|
571
767
|
options?.bitcoinWallet==null ? {
|
|
572
|
-
address: this.address,
|
|
768
|
+
address: this.address ?? "",
|
|
573
769
|
amount: Number(this.amount),
|
|
574
770
|
hyperlink: this._getHyperlink(),
|
|
575
|
-
type: "ADDRESS"
|
|
771
|
+
type: "ADDRESS" as const
|
|
576
772
|
} : {
|
|
577
773
|
...await this.getFundedPsbt(options.bitcoinWallet),
|
|
578
|
-
type: "FUNDED_PSBT"
|
|
774
|
+
type: "FUNDED_PSBT" as const
|
|
579
775
|
}
|
|
580
776
|
]
|
|
581
777
|
}
|
|
582
778
|
];
|
|
583
779
|
}
|
|
584
780
|
|
|
585
|
-
if(this.
|
|
781
|
+
if(this._state===FromBTCSwapState.CLAIM_COMMITED) {
|
|
586
782
|
if(this.getTimeoutTime()<Date.now()) throw new Error("Swap address already expired or close to expiry!");
|
|
587
783
|
return [
|
|
588
784
|
{
|
|
589
785
|
name: "Payment" as const,
|
|
590
786
|
description: "Send funds to the bitcoin swap address",
|
|
591
|
-
chain: "BITCOIN",
|
|
787
|
+
chain: "BITCOIN" as const,
|
|
592
788
|
txs: [
|
|
593
789
|
options?.bitcoinWallet==null ? {
|
|
594
|
-
address: this.
|
|
790
|
+
address: this.getAddress(),
|
|
595
791
|
amount: Number(this.amount),
|
|
596
792
|
hyperlink: this._getHyperlink(),
|
|
597
|
-
type: "ADDRESS"
|
|
793
|
+
type: "ADDRESS" as const
|
|
598
794
|
} : {
|
|
599
795
|
...await this.getFundedPsbt(options.bitcoinWallet),
|
|
600
|
-
type: "FUNDED_PSBT"
|
|
796
|
+
type: "FUNDED_PSBT" as const
|
|
601
797
|
}
|
|
602
798
|
]
|
|
603
799
|
}
|
|
@@ -612,21 +808,16 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
612
808
|
//// Commit
|
|
613
809
|
|
|
614
810
|
/**
|
|
615
|
-
*
|
|
811
|
+
* @inheritDoc
|
|
616
812
|
*
|
|
617
|
-
* @param _signer Signer to sign the transactions with, must be the same as used in the initialization
|
|
618
|
-
* @param abortSignal Abort signal to stop waiting for the transaction confirmation and abort
|
|
619
|
-
* @param skipChecks Skip checks like making sure init signature is still valid and swap wasn't commited yet
|
|
620
|
-
* (this is handled when swap is created (quoted), if you commit right after quoting, you can use skipChecks=true)
|
|
621
|
-
* @param onBeforeTxSent
|
|
622
813
|
* @throws {Error} If invalid signer is provided that doesn't match the swap data
|
|
623
814
|
*/
|
|
624
815
|
async commit(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, skipChecks?: boolean, onBeforeTxSent?: (txId: string) => void): Promise<string> {
|
|
625
|
-
const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper.
|
|
816
|
+
const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
|
|
626
817
|
this.checkSigner(signer);
|
|
627
818
|
let txCount = 0;
|
|
628
819
|
const txs = await this.txsCommit(skipChecks);
|
|
629
|
-
const result = await this.wrapper.
|
|
820
|
+
const result = await this.wrapper._chain.sendAndConfirm(
|
|
630
821
|
signer, txs, true, abortSignal, undefined, (txId: string) => {
|
|
631
822
|
txCount++;
|
|
632
823
|
if(onBeforeTxSent!=null && txCount===txs.length) onBeforeTxSent(txId);
|
|
@@ -634,16 +825,19 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
634
825
|
}
|
|
635
826
|
);
|
|
636
827
|
|
|
637
|
-
this.
|
|
638
|
-
if(this.
|
|
828
|
+
this._commitTxId = result[result.length - 1];
|
|
829
|
+
if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
639
830
|
await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
|
|
640
831
|
}
|
|
641
|
-
return this.
|
|
832
|
+
return this._commitTxId;
|
|
642
833
|
}
|
|
643
834
|
|
|
835
|
+
/**
|
|
836
|
+
* @inheritDoc
|
|
837
|
+
*/
|
|
644
838
|
async waitTillCommited(abortSignal?: AbortSignal): Promise<void> {
|
|
645
|
-
if(this.
|
|
646
|
-
if(this.
|
|
839
|
+
if(this._state===FromBTCSwapState.CLAIM_COMMITED || this._state===FromBTCSwapState.CLAIM_CLAIMED) return Promise.resolve();
|
|
840
|
+
if(this._state!==FromBTCSwapState.PR_CREATED && this._state!==FromBTCSwapState.QUOTE_SOFT_EXPIRED) throw new Error("Invalid state");
|
|
647
841
|
|
|
648
842
|
const abortController = extendAbortController(abortSignal);
|
|
649
843
|
const result = await Promise.race([
|
|
@@ -656,13 +850,13 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
656
850
|
if(result===true) this.logger.debug("waitTillCommited(): Resolved from watchdog - commited");
|
|
657
851
|
if(result===false) {
|
|
658
852
|
this.logger.debug("waitTillCommited(): Resolved from watchdog - signature expired");
|
|
659
|
-
if(this.
|
|
853
|
+
if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
660
854
|
await this._saveAndEmit(FromBTCSwapState.QUOTE_EXPIRED);
|
|
661
855
|
}
|
|
662
856
|
return;
|
|
663
857
|
}
|
|
664
858
|
|
|
665
|
-
if(this.
|
|
859
|
+
if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
666
860
|
await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
|
|
667
861
|
}
|
|
668
862
|
}
|
|
@@ -672,10 +866,16 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
672
866
|
//// Claim
|
|
673
867
|
|
|
674
868
|
/**
|
|
675
|
-
* Returns transactions
|
|
676
|
-
*
|
|
869
|
+
* Returns transactions for settling (claiming) the swap if the swap requires manual settlement, you can check so
|
|
870
|
+
* with isClaimable. After sending the transaction manually be sure to call the waitTillClaimed function to wait
|
|
871
|
+
* till the claim transaction is observed, processed by the SDK and state of the swap properly updated.
|
|
872
|
+
*
|
|
873
|
+
* @remarks
|
|
874
|
+
* Might also return transactions necessary to sync the bitcoin light client.
|
|
875
|
+
*
|
|
876
|
+
* @param _signer Address of the signer to create the claim transactions for
|
|
677
877
|
*
|
|
678
|
-
* @throws {Error} If the swap is in invalid state (must be BTC_TX_CONFIRMED)
|
|
878
|
+
* @throws {Error} If the swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
|
|
679
879
|
*/
|
|
680
880
|
async txsClaim(_signer?: string | T["Signer"] | T["NativeSigner"]): Promise<T["TX"][]> {
|
|
681
881
|
let signer: string | T["Signer"] | undefined = undefined;
|
|
@@ -685,41 +885,52 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
685
885
|
} else if (isAbstractSigner(_signer)) {
|
|
686
886
|
signer = _signer;
|
|
687
887
|
} else {
|
|
688
|
-
signer = await this.wrapper.
|
|
888
|
+
signer = await this.wrapper._chain.wrapSigner(_signer);
|
|
689
889
|
}
|
|
690
890
|
}
|
|
691
891
|
|
|
692
|
-
if(this.
|
|
892
|
+
if(this._state!==FromBTCSwapState.BTC_TX_CONFIRMED) throw new Error("Must be in BTC_TX_CONFIRMED state!");
|
|
693
893
|
if(this.txId==null || this.vout==null) throw new Error("Bitcoin transaction ID not known!");
|
|
694
894
|
|
|
695
|
-
const tx = await this.wrapper.
|
|
895
|
+
const tx = await this.wrapper._btcRpc.getTransaction(this.txId);
|
|
696
896
|
if(tx==null) throw new Error("Bitcoin transaction not found on the network!");
|
|
897
|
+
|
|
898
|
+
this.requiredConfirmations ??= this.inferRequiredConfirmationsCount(tx, this.vout);
|
|
899
|
+
if(this.requiredConfirmations==null)
|
|
900
|
+
throw new Error("Cannot create claim transaction, because required confirmations are not known and cannot be infered! This can happen after a swap is recovered.");
|
|
901
|
+
|
|
697
902
|
if(tx.blockhash==null || tx.confirmations==null || tx.blockheight==null || tx.confirmations<this.requiredConfirmations)
|
|
698
903
|
throw new Error("Bitcoin transaction not confirmed yet!");
|
|
699
904
|
|
|
700
|
-
return await this.wrapper.
|
|
905
|
+
return await this.wrapper._contract.txsClaimWithTxData(signer ?? this._getInitiator(), this._data, {
|
|
701
906
|
blockhash: tx.blockhash,
|
|
702
907
|
confirmations: tx.confirmations,
|
|
703
908
|
txid: tx.txid,
|
|
704
909
|
hex: tx.hex,
|
|
705
910
|
height: tx.blockheight
|
|
706
|
-
}, this.requiredConfirmations, this.vout, undefined, this.wrapper.
|
|
911
|
+
}, this.requiredConfirmations, this.vout, undefined, this.wrapper._synchronizer, true);
|
|
707
912
|
}
|
|
708
913
|
|
|
709
914
|
/**
|
|
710
|
-
*
|
|
915
|
+
* Settles the swap by claiming the funds on the destination chain if the swap requires manual settlement, you can
|
|
916
|
+
* check so with isClaimable.
|
|
917
|
+
*
|
|
918
|
+
* @remarks
|
|
919
|
+
* Might also sync the bitcoin light client during the process.
|
|
920
|
+
*
|
|
921
|
+
* @param _signer Signer to use for signing the settlement transactions, can also be different to the recipient
|
|
922
|
+
* @param abortSignal Abort signal
|
|
923
|
+
* @param onBeforeTxSent Optional callback triggered before the claim transaction is broadcasted
|
|
711
924
|
*
|
|
712
|
-
* @
|
|
713
|
-
* @param abortSignal Abort signal to stop waiting for transaction confirmation
|
|
714
|
-
* @param onBeforeTxSent
|
|
925
|
+
* @returns Transaction ID of the settlement (claim) transaction on the destination smart chain
|
|
715
926
|
*/
|
|
716
927
|
async claim(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, onBeforeTxSent?: (txId: string) => void): Promise<string> {
|
|
717
|
-
const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper.
|
|
928
|
+
const signer = isAbstractSigner(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
|
|
718
929
|
let txIds: string[];
|
|
719
930
|
try {
|
|
720
931
|
let txCount = 0;
|
|
721
932
|
const txs = await this.txsClaim(signer);
|
|
722
|
-
txIds = await this.wrapper.
|
|
933
|
+
txIds = await this.wrapper._chain.sendAndConfirm(
|
|
723
934
|
signer, txs, true, abortSignal, undefined, (txId: string) => {
|
|
724
935
|
txCount++;
|
|
725
936
|
if(onBeforeTxSent!=null && txCount===txs.length) onBeforeTxSent(txId);
|
|
@@ -728,26 +939,26 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
728
939
|
);
|
|
729
940
|
} catch (e) {
|
|
730
941
|
this.logger.info("claim(): Failed to claim ourselves, checking swap claim state...");
|
|
731
|
-
if(this.
|
|
942
|
+
if(this._state===FromBTCSwapState.CLAIM_CLAIMED) {
|
|
732
943
|
this.logger.info("claim(): Transaction state is CLAIM_CLAIMED, swap was successfully claimed by the watchtower");
|
|
733
|
-
return this.
|
|
944
|
+
return this._claimTxId!;
|
|
734
945
|
}
|
|
735
|
-
const status = await this.wrapper.
|
|
946
|
+
const status = await this.wrapper._contract.getCommitStatus(this._getInitiator(), this._data);
|
|
736
947
|
if(status?.type===SwapCommitStateType.PAID) {
|
|
737
948
|
this.logger.info("claim(): Transaction commit status is PAID, swap was successfully claimed by the watchtower");
|
|
738
|
-
if(this.
|
|
949
|
+
if(this._claimTxId==null) this._claimTxId = await status.getClaimTxId();
|
|
739
950
|
const txId = Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
|
|
740
951
|
await this._setBitcoinTxId(txId);
|
|
741
952
|
await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
|
|
742
|
-
return this.
|
|
953
|
+
return this._claimTxId;
|
|
743
954
|
}
|
|
744
955
|
throw e;
|
|
745
956
|
}
|
|
746
957
|
|
|
747
|
-
this.
|
|
958
|
+
this._claimTxId = txIds[txIds.length - 1];
|
|
748
959
|
if(
|
|
749
|
-
this.
|
|
750
|
-
this.
|
|
960
|
+
this._state===FromBTCSwapState.CLAIM_COMMITED || this._state===FromBTCSwapState.BTC_TX_CONFIRMED ||
|
|
961
|
+
this._state===FromBTCSwapState.EXPIRED || this._state===FromBTCSwapState.FAILED
|
|
751
962
|
) {
|
|
752
963
|
await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
|
|
753
964
|
}
|
|
@@ -755,17 +966,14 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
755
966
|
}
|
|
756
967
|
|
|
757
968
|
/**
|
|
758
|
-
*
|
|
969
|
+
* @inheritDoc
|
|
759
970
|
*
|
|
760
|
-
* @
|
|
761
|
-
* @param abortSignal AbortSignal
|
|
762
|
-
* @throws {Error} If swap is in invalid state (must be BTC_TX_CONFIRMED)
|
|
971
|
+
* @throws {Error} If swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
|
|
763
972
|
* @throws {Error} If the LP refunded sooner than we were able to claim
|
|
764
|
-
* @returns {boolean} whether the swap was claimed in time or not
|
|
765
973
|
*/
|
|
766
974
|
async waitTillClaimed(maxWaitTimeSeconds?: number, abortSignal?: AbortSignal): Promise<boolean> {
|
|
767
|
-
if(this.
|
|
768
|
-
if(this.
|
|
975
|
+
if(this._state===FromBTCSwapState.CLAIM_CLAIMED) return Promise.resolve(true);
|
|
976
|
+
if(this._state!==FromBTCSwapState.BTC_TX_CONFIRMED) throw new Error("Invalid state (not BTC_TX_CONFIRMED)");
|
|
769
977
|
|
|
770
978
|
const abortController = extendAbortController(abortSignal);
|
|
771
979
|
|
|
@@ -803,8 +1011,8 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
803
1011
|
this.logger.debug("waitTillClaimed(): Resolved from watchdog");
|
|
804
1012
|
|
|
805
1013
|
if(res?.type===SwapCommitStateType.PAID) {
|
|
806
|
-
if((this.
|
|
807
|
-
if(this.
|
|
1014
|
+
if((this._state as FromBTCSwapState)!==FromBTCSwapState.CLAIM_CLAIMED) {
|
|
1015
|
+
if(this._claimTxId==null) this._claimTxId = await res.getClaimTxId();
|
|
808
1016
|
const txId = Buffer.from(await res.getClaimResult(), "hex").reverse().toString("hex");
|
|
809
1017
|
await this._setBitcoinTxId(txId);
|
|
810
1018
|
await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
|
|
@@ -812,10 +1020,10 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
812
1020
|
}
|
|
813
1021
|
if(res?.type===SwapCommitStateType.NOT_COMMITED || res?.type===SwapCommitStateType.EXPIRED) {
|
|
814
1022
|
if(
|
|
815
|
-
(this.
|
|
816
|
-
(this.
|
|
1023
|
+
(this._state as FromBTCSwapState)!==FromBTCSwapState.CLAIM_CLAIMED &&
|
|
1024
|
+
(this._state as FromBTCSwapState)!==FromBTCSwapState.FAILED
|
|
817
1025
|
) {
|
|
818
|
-
if(res.getRefundTxId!=null) this.
|
|
1026
|
+
if(res.getRefundTxId!=null) this._refundTxId = await res.getRefundTxId();
|
|
819
1027
|
await this._saveAndEmit(FromBTCSwapState.FAILED);
|
|
820
1028
|
}
|
|
821
1029
|
throw new Error("Swap expired while waiting for claim!");
|
|
@@ -828,11 +1036,14 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
828
1036
|
//////////////////////////////
|
|
829
1037
|
//// Storage
|
|
830
1038
|
|
|
1039
|
+
/**
|
|
1040
|
+
* @inheritDoc
|
|
1041
|
+
*/
|
|
831
1042
|
serialize(): any {
|
|
832
1043
|
return {
|
|
833
1044
|
...super.serialize(),
|
|
834
1045
|
address: this.address,
|
|
835
|
-
amount: this.amount.toString(10),
|
|
1046
|
+
amount: this.amount==null ? null: this.amount.toString(10),
|
|
836
1047
|
requiredConfirmations: this.requiredConfirmations,
|
|
837
1048
|
senderAddress: this.senderAddress,
|
|
838
1049
|
txId: this.txId,
|
|
@@ -851,105 +1062,132 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
851
1062
|
* @private
|
|
852
1063
|
*/
|
|
853
1064
|
private async syncStateFromChain(quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState): Promise<boolean> {
|
|
854
|
-
if(
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
return true;
|
|
865
|
-
case SwapCommitStateType.PAID:
|
|
866
|
-
if(this.claimTxId==null) this.claimTxId = await status.getClaimTxId();
|
|
867
|
-
const txId = Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
|
|
868
|
-
await this._setBitcoinTxId(txId);
|
|
869
|
-
this.state = FromBTCSwapState.CLAIM_CLAIMED;
|
|
870
|
-
return true;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
if(quoteExpired) {
|
|
874
|
-
this.state = FromBTCSwapState.QUOTE_EXPIRED;
|
|
875
|
-
return true;
|
|
1065
|
+
if(
|
|
1066
|
+
this._state===FromBTCSwapState.PR_CREATED ||
|
|
1067
|
+
this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
|
|
1068
|
+
this._state===FromBTCSwapState.CLAIM_COMMITED ||
|
|
1069
|
+
this._state===FromBTCSwapState.BTC_TX_CONFIRMED ||
|
|
1070
|
+
this._state===FromBTCSwapState.EXPIRED
|
|
1071
|
+
) {
|
|
1072
|
+
let quoteExpired: boolean = false;
|
|
1073
|
+
if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1074
|
+
quoteExpired = quoteDefinitelyExpired ?? await this._verifyQuoteDefinitelyExpired(); //Make sure we check for expiry here, to prevent race conditions
|
|
876
1075
|
}
|
|
877
1076
|
|
|
878
|
-
|
|
879
|
-
|
|
1077
|
+
const status = commitStatus ?? await this.wrapper._contract.getCommitStatus(this._getInitiator(), this._data);
|
|
1078
|
+
if(status!=null && await this._forciblySetOnchainState(status)) return true;
|
|
880
1079
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
case SwapCommitStateType.PAID:
|
|
885
|
-
if(this.claimTxId==null) this.claimTxId = await status.getClaimTxId();
|
|
886
|
-
const txId = Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
|
|
887
|
-
await this._setBitcoinTxId(txId);
|
|
888
|
-
this.state = FromBTCSwapState.CLAIM_CLAIMED;
|
|
1080
|
+
if(this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
1081
|
+
if(quoteExpired) {
|
|
1082
|
+
this._state = FromBTCSwapState.QUOTE_EXPIRED;
|
|
889
1083
|
return true;
|
|
890
|
-
|
|
891
|
-
case SwapCommitStateType.EXPIRED:
|
|
892
|
-
if(this.refundTxId==null && status.getRefundTxId) this.refundTxId = await status.getRefundTxId();
|
|
893
|
-
this.state = FromBTCSwapState.FAILED;
|
|
894
|
-
return true;
|
|
895
|
-
case SwapCommitStateType.COMMITED:
|
|
896
|
-
const res = await this.getBitcoinPayment();
|
|
897
|
-
if(res!=null) {
|
|
898
|
-
let save: boolean = false;
|
|
899
|
-
if(this.txId!==res.txId) {
|
|
900
|
-
if(res.inputAddresses!=null) this.senderAddress = res.inputAddresses[0];
|
|
901
|
-
this.txId = res.txId;
|
|
902
|
-
this.vout = res.vout;
|
|
903
|
-
save = true;
|
|
904
|
-
}
|
|
905
|
-
if(res.confirmations>=this.requiredConfirmations) {
|
|
906
|
-
this.state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
907
|
-
save = true;
|
|
908
|
-
}
|
|
909
|
-
return save;
|
|
910
|
-
}
|
|
911
|
-
break;
|
|
1084
|
+
}
|
|
912
1085
|
}
|
|
913
1086
|
}
|
|
914
1087
|
|
|
915
1088
|
return false;
|
|
916
1089
|
}
|
|
917
1090
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1091
|
+
/**
|
|
1092
|
+
* @inheritDoc
|
|
1093
|
+
* @internal
|
|
1094
|
+
*/
|
|
1095
|
+
_shouldFetchOnchainState(): boolean {
|
|
1096
|
+
return this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
|
|
1097
|
+
this._state===FromBTCSwapState.CLAIM_COMMITED || this._state===FromBTCSwapState.BTC_TX_CONFIRMED ||
|
|
1098
|
+
this._state===FromBTCSwapState.EXPIRED;
|
|
922
1099
|
}
|
|
923
1100
|
|
|
1101
|
+
/**
|
|
1102
|
+
* @inheritDoc
|
|
1103
|
+
* @internal
|
|
1104
|
+
*/
|
|
924
1105
|
_shouldFetchExpiryStatus(): boolean {
|
|
925
|
-
return this.
|
|
1106
|
+
return this._state===FromBTCSwapState.PR_CREATED || this._state===FromBTCSwapState.QUOTE_SOFT_EXPIRED;
|
|
926
1107
|
}
|
|
927
1108
|
|
|
1109
|
+
/**
|
|
1110
|
+
* @inheritDoc
|
|
1111
|
+
* @internal
|
|
1112
|
+
*/
|
|
928
1113
|
async _sync(save?: boolean, quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState): Promise<boolean> {
|
|
929
1114
|
const changed = await this.syncStateFromChain(quoteDefinitelyExpired, commitStatus);
|
|
930
1115
|
if(changed && save) await this._saveAndEmit();
|
|
931
1116
|
return changed;
|
|
932
1117
|
}
|
|
933
1118
|
|
|
1119
|
+
/**
|
|
1120
|
+
* @inheritDoc
|
|
1121
|
+
* @internal
|
|
1122
|
+
*/
|
|
1123
|
+
async _forciblySetOnchainState(status: SwapCommitState): Promise<boolean> {
|
|
1124
|
+
switch(status.type) {
|
|
1125
|
+
case SwapCommitStateType.PAID:
|
|
1126
|
+
if(this._claimTxId==null) this._claimTxId = await status.getClaimTxId();
|
|
1127
|
+
const txId = Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
|
|
1128
|
+
await this._setBitcoinTxId(txId);
|
|
1129
|
+
this._state = FromBTCSwapState.CLAIM_CLAIMED;
|
|
1130
|
+
return true;
|
|
1131
|
+
case SwapCommitStateType.NOT_COMMITED:
|
|
1132
|
+
if(this._refundTxId==null && status.getRefundTxId) this._refundTxId = await status.getRefundTxId();
|
|
1133
|
+
if(this._refundTxId!=null) {
|
|
1134
|
+
this._state = FromBTCSwapState.FAILED;
|
|
1135
|
+
return true;
|
|
1136
|
+
}
|
|
1137
|
+
break;
|
|
1138
|
+
case SwapCommitStateType.EXPIRED:
|
|
1139
|
+
if(this._refundTxId==null && status.getRefundTxId) this._refundTxId = await status.getRefundTxId();
|
|
1140
|
+
this._state = this._refundTxId==null ? FromBTCSwapState.QUOTE_EXPIRED : FromBTCSwapState.FAILED;
|
|
1141
|
+
return true;
|
|
1142
|
+
case SwapCommitStateType.COMMITED:
|
|
1143
|
+
let save: boolean = false;
|
|
1144
|
+
if(this._state!==FromBTCSwapState.CLAIM_COMMITED && this._state!==FromBTCSwapState.BTC_TX_CONFIRMED && this._state!==FromBTCSwapState.EXPIRED) {
|
|
1145
|
+
this._state = FromBTCSwapState.CLAIM_COMMITED;
|
|
1146
|
+
save = true;
|
|
1147
|
+
}
|
|
1148
|
+
if(this.address==null) return save;
|
|
1149
|
+
|
|
1150
|
+
const res = await this.getBitcoinPayment();
|
|
1151
|
+
if(res!=null) {
|
|
1152
|
+
if(this.txId!==res.txId) {
|
|
1153
|
+
if(res.inputAddresses!=null) this.senderAddress = res.inputAddresses[0];
|
|
1154
|
+
this.txId = res.txId;
|
|
1155
|
+
this.vout = res.vout;
|
|
1156
|
+
save = true;
|
|
1157
|
+
}
|
|
1158
|
+
if(this.requiredConfirmations!=null && res.confirmations>=this.requiredConfirmations) {
|
|
1159
|
+
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
1160
|
+
save = true;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
return save;
|
|
1164
|
+
}
|
|
1165
|
+
return false;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/**
|
|
1169
|
+
* @inheritDoc
|
|
1170
|
+
* @internal
|
|
1171
|
+
*/
|
|
934
1172
|
async _tick(save?: boolean): Promise<boolean> {
|
|
935
|
-
switch(this.
|
|
1173
|
+
switch(this._state) {
|
|
936
1174
|
case FromBTCSwapState.PR_CREATED:
|
|
937
1175
|
if(this.expiry<Date.now()) {
|
|
938
|
-
this.
|
|
1176
|
+
this._state = FromBTCSwapState.QUOTE_SOFT_EXPIRED;
|
|
939
1177
|
if(save) await this._saveAndEmit();
|
|
940
1178
|
return true;
|
|
941
1179
|
}
|
|
942
1180
|
break;
|
|
943
1181
|
case FromBTCSwapState.CLAIM_COMMITED:
|
|
944
1182
|
if(this.getTimeoutTime()<Date.now()) {
|
|
945
|
-
this.
|
|
1183
|
+
this._state = FromBTCSwapState.EXPIRED;
|
|
946
1184
|
if(save) await this._saveAndEmit();
|
|
947
1185
|
return true;
|
|
948
1186
|
}
|
|
949
1187
|
case FromBTCSwapState.EXPIRED:
|
|
950
1188
|
//Check if bitcoin payment was received every 2 minutes
|
|
951
1189
|
if(Math.floor(Date.now()/1000)%120===0) {
|
|
952
|
-
try {
|
|
1190
|
+
if(this.address!=null) try {
|
|
953
1191
|
const res = await this.getBitcoinPayment();
|
|
954
1192
|
if(res!=null) {
|
|
955
1193
|
let shouldSave: boolean = false;
|
|
@@ -959,8 +1197,8 @@ export class FromBTCSwap<T extends ChainType = ChainType>
|
|
|
959
1197
|
if(res.inputAddresses!=null) this.senderAddress = res.inputAddresses[0];
|
|
960
1198
|
shouldSave = true;
|
|
961
1199
|
}
|
|
962
|
-
if(res.confirmations>=this.requiredConfirmations) {
|
|
963
|
-
this.
|
|
1200
|
+
if(this.requiredConfirmations!=null && res.confirmations>=this.requiredConfirmations) {
|
|
1201
|
+
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
964
1202
|
if(save) await this._saveAndEmit();
|
|
965
1203
|
shouldSave = true;
|
|
966
1204
|
}
|